diff --git a/.replit b/.replit new file mode 100644 index 0000000..331d952 --- /dev/null +++ b/.replit @@ -0,0 +1,33 @@ +modules = ["nodejs-20"] + +[nix] +channel = "stable-24_05" + +[workflows] +runButton = "Project" + +[[workflows.workflow]] +name = "Project" +mode = "parallel" +author = "agent" + +[[workflows.workflow.tasks]] +task = "workflow.run" +args = "Build VSCode Extension" + +[[workflows.workflow]] +name = "Build VSCode Extension" +author = "agent" + +[workflows.workflow.metadata] +agentRequireRestartOnSave = false + +[[workflows.workflow.tasks]] +task = "packager.installForAll" + +[[workflows.workflow.tasks]] +task = "shell.exec" +args = "npm run compile && npx @vscode/vsce package" + +[deployment] +run = ["sh", "-c", "npm run compile && npx @vscode/vsce package"] diff --git a/.vscodeignore b/.vscodeignore new file mode 100644 index 0000000..e0c6b8d --- /dev/null +++ b/.vscodeignore @@ -0,0 +1,28 @@ +.vscode/** +.vscode-test/** +node_modules/** +src/** +.gitignore +.yarnrc +**/tsconfig.json +**/.eslintrc.json +**/*.map +**/*.ts +.replit +.breakpoints +.local/** +.cache/** +.upm/** +.git/** +.github/** +**/test/** +**/tests/** +**/*.test.js +**/*.spec.js +*.vsix +# Development and build artifacts +generated-icon.png +out/test/** +package-lock.json +# Assets not needed in the final package +attached_assets/** \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d25d351 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# Change Log + +All notable changes to the "prompter" extension will be documented in this file. + +Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. + +## [Unreleased] + +- Initial release \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6c43271 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright 2025 Abhishek Bhakat + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a265de2 --- /dev/null +++ b/README.md @@ -0,0 +1,153 @@ +# Prompter + +Code smarter with AI—no more messy copy-pasting. Prompter structures your prompts and applies AI changes seamlessly, streamlining your coding workflow. + +## Why Prompter? + +- **Too much bloat in your repo?** Stop zipping everything—send only the files that matter. +- **LLM underperforming?** Cut the noise for sharper, more accurate responses. +- **Better AI coding?** Select just the right context to optimize results. + +Prompter empowers you to work efficiently with AI, reducing token waste and improving clarity. + +## Features + +- **Advanced File Selection & Token Estimation** + Precisely filter files and estimate token usage instantly for optimized, cost-effective prompts. + +- **Optimized XML Prompt** + Structured file trees, CodeMaps, content, and instructions in XML for maximum LLM clarity. + +- **Structured XML Diffs** + Converts LLM-generated XML edits into precise, reviewable diffs—works at any file size. + +- **Codemap Extraction** + Scans files locally to extract classes, functions, and references, minimizing tokens and hallucinations. Auto-detects referenced types. + +- **Mac-Native Performance** + Built for macOS with native speed and responsiveness—because performance matters. + +- **Clipboard Integration** + Copy structured prompts into any AI chat app—your data stays local, no external API needed. + +- **Works with Any Model** + Compatible with OpenAI, Anthropic, DeepSeek, Gemini, Azure, OpenRouter, and local models—private and offline when you need it. + +- **Privacy First** + Local models, offline scanning, and direct clipboard use—no intermediaries required. + +## Installation + +*(Note: Installation steps are assumed based on the VS Code context from other files. Adjust as needed.)* +1. Clone the repository: + ```bash + git clone + ``` +2. Open the project in VS Code. +3. Install dependencies: + ```bash + npm install + ``` +4. Build the extension: + ```bash + npm run compile + ``` +5. Press `F5` in VS Code to launch the extension in a development window. + +## Usage + +1. Open your project in VS Code. +2. Use the Prompter interface to select files and estimate tokens. +3. Generate a structured XML prompt via the clipboard. +4. Paste into your preferred AI model (e.g., ChatGPT, Claude, or a local LLM). +5. Apply the returned XML diffs directly through Prompter for seamless integration. + +## Contributing + +We welcome contributions! To get started: +1. Fork the repository. +2. Create a feature branch: `git checkout -b my-feature`. +3. Commit your changes: `git commit -m "Add my feature"`. +4. Push to your branch: `git push origin my-feature`. +5. Open a pull request. + +See `vsc-extension-quickstart.md` for development setup and testing details. + +--- +Built with ❤️ by the Prompter team. + +Code smarter with AI—no more messy copy-pasting. Prompter structures your prompts and applies AI changes seamlessly, streamlining your coding workflow. + +## Why Prompter? + +- **Too much bloat in your repo?** Stop zipping everything—send only the files that matter. +- **LLM underperforming?** Cut the noise for sharper, more accurate responses. +- **Better AI coding?** Select just the right context to optimize results. + +Prompter empowers you to work efficiently with AI, reducing token waste and improving clarity. + +## Features + +- **Advanced File Selection & Token Estimation** + Precisely filter files and estimate token usage instantly for optimized, cost-effective prompts. + +- **Optimized XML Prompt** + Structured file trees, CodeMaps, content, and instructions in XML for maximum LLM clarity. + +- **Structured XML Diffs** + Converts LLM-generated XML edits into precise, reviewable diffs—works at any file size. + +- **Codemap Extraction** + Scans files locally to extract classes, functions, and references, minimizing tokens and hallucinations. Auto-detects referenced types. + +- **Mac-Native Performance** + Built for macOS with native speed and responsiveness—because performance matters. + +- **Clipboard Integration** + Copy structured prompts into any AI chat app—your data stays local, no external API needed. + +- **Works with Any Model** + Compatible with OpenAI, Anthropic, DeepSeek, Gemini, Azure, OpenRouter, and local models—private and offline when you need it. + +- **Privacy First** + Local models, offline scanning, and direct clipboard use—no intermediaries required. + +## Installation + +*(Note: Installation steps are assumed based on the VS Code context from other files. Adjust as needed.)* +1. Clone the repository: + ```bash + git clone + ``` +2. Open the project in VS Code. +3. Install dependencies: + ```bash + npm install + ``` +4. Build the extension: + ```bash + npm run compile + ``` +5. Press `F5` in VS Code to launch the extension in a development window. + +## Usage + +1. Open your project in VS Code. +2. Use the Prompter interface to select files and estimate tokens. +3. Generate a structured XML prompt via the clipboard. +4. Paste into your preferred AI model (e.g., ChatGPT, Claude, or a local LLM). +5. Apply the returned XML diffs directly through Prompter for seamless integration. + +## Contributing + +We welcome contributions! To get started: +1. Fork the repository. +2. Create a feature branch: `git checkout -b my-feature`. +3. Commit your changes: `git commit -m "Add my feature"`. +4. Push to your branch: `git push origin my-feature`. +5. Open a pull request. + +See `vsc-extension-quickstart.md` for development setup and testing details. + +--- +Built with ❤️ by the Prompter team. diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..d5c0b53 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,28 @@ +import typescriptEslint from "@typescript-eslint/eslint-plugin"; +import tsParser from "@typescript-eslint/parser"; + +export default [{ + files: ["**/*.ts"], +}, { + plugins: { + "@typescript-eslint": typescriptEslint, + }, + + languageOptions: { + parser: tsParser, + ecmaVersion: 2022, + sourceType: "module", + }, + + rules: { + "@typescript-eslint/naming-convention": ["warn", { + selector: "import", + format: ["camelCase", "PascalCase"], + }], + + curly: "warn", + eqeqeq: "warn", + "no-throw-literal": "warn", + semi: "warn", + }, +}]; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..7c1296d --- /dev/null +++ b/package.json @@ -0,0 +1,80 @@ +{ + "name": "prompter", + "displayName": "Prompter", + "description": "Easy prompt generation and apply edits using prompter.", + "version": "0.0.1", + "publisher": "abhishekbhakat", + "engines": { + "vscode": "^1.98.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [], + "main": "./out/extension.js", + "contributes": { + "commands": [ + { + "command": "prompter.toggleSelection", + "title": "Toggle File Selection" + }, + { + "command": "prompter.generatePrompt", + "title": "Generate XML Prompt" + } + ], + "viewsContainers": { + "activitybar": [ + { + "id": "prompter-sidebar", + "title": "Prompter", + "icon": "resources/icon.svg" + } + ] + }, + "views": { + "prompter-sidebar": [ + { + "id": "prompterView", + "name": "Prompter" + } + ] + }, + "menus": { + "view/title": [ + { + "command": "prompter.generatePrompt", + "group": "navigation" + } + ], + "view/item/context": [ + { + "command": "prompter.toggleSelection", + "when": "view == prompterView" + } + ] + } + }, + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "tsc -p ./", + "watch": "tsc -watch -p ./", + "pretest": "npm run compile && npm run lint", + "lint": "eslint src", + "test": "vscode-test" + }, + "devDependencies": { + "@types/mocha": "^10.0.10", + "@types/node": "20.x", + "@types/vscode": "^1.98.0", + "@typescript-eslint/eslint-plugin": "^8.25.0", + "@typescript-eslint/parser": "^8.25.0", + "@vscode/test-cli": "^0.0.10", + "@vscode/test-electron": "^2.4.1", + "eslint": "^9.21.0", + "typescript": "^5.7.3" + }, + "dependencies": { + "@vscode/vsce": "^3.2.2" + } +} diff --git a/resources/icon.svg b/resources/icon.svg new file mode 100644 index 0000000..716a8f2 --- /dev/null +++ b/resources/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/extension.ts b/src/extension.ts new file mode 100644 index 0000000..9c503f5 --- /dev/null +++ b/src/extension.ts @@ -0,0 +1,234 @@ +// The module 'vscode' contains the VS Code extensibility API +import * as vscode from 'vscode'; +import * as path from 'path'; + +// Custom TreeItem for files and folders +class FileTreeItem extends vscode.TreeItem { + constructor( + public readonly resourceUri: vscode.Uri, + public readonly collapsibleState: vscode.TreeItemCollapsibleState, + public readonly isDirectory: boolean + ) { + super(resourceUri, collapsibleState); + this.contextValue = isDirectory ? 'directory' : 'file'; + this.tooltip = resourceUri.fsPath; + this.description = isDirectory ? 'folder' : path.extname(resourceUri.fsPath); + this.checkboxState = vscode.TreeItemCheckboxState.Unchecked; + } +} + +// TreeView provider for the sidebar +class PrompterTreeProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; + private selectedFiles: Set = new Set(); + private xmlEditsEnabled: boolean = false; + + constructor(private workspaceRoot: string | undefined) {} + + getTreeItem(element: FileTreeItem): vscode.TreeItem { + const item = element; + if (this.selectedFiles.has(element.resourceUri.fsPath)) { + item.checkboxState = vscode.TreeItemCheckboxState.Checked; + item.label = path.basename(element.resourceUri.fsPath); + } else { + item.checkboxState = vscode.TreeItemCheckboxState.Unchecked; + item.label = path.basename(element.resourceUri.fsPath); + } + return item; + } + + async getChildren(element?: FileTreeItem): Promise { + if (!this.workspaceRoot) { + vscode.window.showInformationMessage('No workspace folder is opened'); + return Promise.resolve([]); + } + + if (element) { + const dirPath = element.resourceUri.fsPath; + return this.getFilesInDirectory(dirPath); + } else { + return this.getFilesInDirectory(this.workspaceRoot); + } + } + + private async getFilesInDirectory(dirPath: string): Promise { + try { + const files = await vscode.workspace.fs.readDirectory(vscode.Uri.file(dirPath)); + return Promise.all(files.map(async ([name, type]) => { + const filePath = path.join(dirPath, name); + const uri = vscode.Uri.file(filePath); + const isDirectory = (type & vscode.FileType.Directory) !== 0; + + return new FileTreeItem( + uri, + isDirectory ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None, + isDirectory + ); + })); + } catch (error) { + console.error(`Error reading directory ${dirPath}:`, error); + return []; + } + } + + toggleSelection(item: FileTreeItem): void { + const filePath = item.resourceUri.fsPath; + if (this.selectedFiles.has(filePath)) { + this.selectedFiles.delete(filePath); + } else { + this.selectedFiles.add(filePath); + } + this._onDidChangeTreeData.fire(); + } + + getSelectedFiles(): Set { + return this.selectedFiles; + } + + toggleXmlEdits(): void { + this.xmlEditsEnabled = !this.xmlEditsEnabled; + } + + isXmlEditsEnabled(): boolean { + return this.xmlEditsEnabled; + } + + refresh(): void { + this._onDidChangeTreeData.fire(); + } +} + +// Utility function to estimate tokens in text +function estimateTokens(text: string): number { + // Rough estimation: Split by whitespace and punctuation + // This is a simple approximation, actual token count may vary by model + const words = text.split(/[\s\p{P}]+/u).filter(Boolean); + return Math.ceil(words.length * 1.3); // Add 30% overhead for special tokens +} + +// Function to read file content +async function readFileContent(filePath: string): Promise { + try { + const readData = await vscode.workspace.fs.readFile(vscode.Uri.file(filePath)); + return Buffer.from(readData).toString('utf8'); + } catch (error) { + console.error(`Error reading file ${filePath}:`, error); + return ''; + } +} + +// Function to generate XML prompt +function generateXMLPrompt(files: Map, xmlEdits: boolean): string { + const xmlParts = ['', '']; + + // Add files section + xmlParts.push(' '); + for (const [path, { content, tokens }] of files) { + xmlParts.push(' '); + xmlParts.push(` ${path}`); + xmlParts.push(` ${tokens}`); + xmlParts.push(` `); + xmlParts.push(' '); + } + xmlParts.push(' '); + + // Add XML edits flag if enabled + if (xmlEdits) { + xmlParts.push(' '); + xmlParts.push(' true'); + xmlParts.push(' '); + } + + xmlParts.push(''); + return xmlParts.join('\n'); +} + +// This method is called when your extension is activated +export function activate(context: vscode.ExtensionContext) { + console.log('Congratulations, your extension "prompter" is now active!'); + + const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath; + const prompterTreeProvider = new PrompterTreeProvider(workspaceRoot); + + // Register the TreeView + const treeView = vscode.window.createTreeView('prompterView', { + treeDataProvider: prompterTreeProvider, + showCollapseAll: true + }); + + // Create XML edits toggle button + const xmlEditsButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); + xmlEditsButton.text = "$(diff-added) XML Edits"; + xmlEditsButton.tooltip = "Toggle XML Edits mode"; + xmlEditsButton.command = 'prompter.toggleXmlEdits'; + xmlEditsButton.show(); + + // Create copy button + const copyButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right); + copyButton.text = "$(clippy) Copy"; + copyButton.tooltip = "Generate and copy XML prompt"; + copyButton.command = 'prompter.generatePrompt'; + copyButton.show(); + + // Register command to toggle file selection + let toggleSelectionCommand = vscode.commands.registerCommand('prompter.toggleSelection', (item: FileTreeItem) => { + prompterTreeProvider.toggleSelection(item); + }); + + // Register command to toggle XML edits + let toggleXmlEditsCommand = vscode.commands.registerCommand('prompter.toggleXmlEdits', () => { + prompterTreeProvider.toggleXmlEdits(); + xmlEditsButton.text = prompterTreeProvider.isXmlEditsEnabled() ? + "$(check) XML Edits" : "$(diff-added) XML Edits"; + }); + + // Register command to generate prompt from selected files + let generatePromptCommand = vscode.commands.registerCommand('prompter.generatePrompt', async () => { + const selectedFiles = prompterTreeProvider.getSelectedFiles(); + if (selectedFiles.size === 0) { + vscode.window.showInformationMessage('Please select files first'); + return; + } + + let totalTokens = 0; + const fileContents = new Map(); + + // Process each selected file + for (const filePath of selectedFiles) { + const content = await readFileContent(filePath); + const tokens = estimateTokens(content); + totalTokens += tokens; + fileContents.set(filePath, { content, tokens }); + + // Show individual file token count + vscode.window.showInformationMessage( + `File: ${filePath}\nEstimated tokens: ${tokens}` + ); + } + + // Show total token count + vscode.window.showInformationMessage( + `Total estimated tokens for ${selectedFiles.size} files: ${totalTokens}` + ); + + // Generate XML prompt + const xmlPrompt = generateXMLPrompt(fileContents, prompterTreeProvider.isXmlEditsEnabled()); + + // Copy to clipboard + await vscode.env.clipboard.writeText(xmlPrompt); + vscode.window.showInformationMessage('XML prompt copied to clipboard!'); + }); + + context.subscriptions.push( + treeView, + xmlEditsButton, + copyButton, + toggleSelectionCommand, + toggleXmlEditsCommand, + generatePromptCommand + ); +} + +// This method is called when your extension is deactivated +export function deactivate() {} \ No newline at end of file diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts new file mode 100644 index 0000000..4ca0ab4 --- /dev/null +++ b/src/test/extension.test.ts @@ -0,0 +1,15 @@ +import * as assert from 'assert'; + +// You can import and use all API from the 'vscode' module +// as well as import your extension to test it +import * as vscode from 'vscode'; +// import * as myExtension from '../../extension'; + +suite('Extension Test Suite', () => { + vscode.window.showInformationMessage('Start all tests.'); + + test('Sample test', () => { + assert.strictEqual(-1, [1, 2, 3].indexOf(5)); + assert.strictEqual(-1, [1, 2, 3].indexOf(0)); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..356580f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "module": "Node16", + "target": "ES2022", + "outDir": "out", + "lib": [ + "ES2022" + ], + "sourceMap": true, + "rootDir": "src", + "strict": true, /* enable all strict type-checking options */ + /* Additional Checks */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + } +} diff --git a/vsc-extension-quickstart.md b/vsc-extension-quickstart.md new file mode 100644 index 0000000..c912064 --- /dev/null +++ b/vsc-extension-quickstart.md @@ -0,0 +1,44 @@ +# Welcome to your VS Code Extension + +## What's in the folder + +* This folder contains all of the files necessary for your extension. +* `package.json` - this is the manifest file in which you declare your extension and command. + * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. +* `src/extension.ts` - this is the main file where you will provide the implementation of your command. + * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. + * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. + +## Get up and running straight away + +* Press `F5` to open a new window with your extension loaded. +* Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. +* Set breakpoints in your code inside `src/extension.ts` to debug your extension. +* Find output from your extension in the debug console. + +## Make changes + +* You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. +* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. + +## Explore the API + +* You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. + +## Run tests + +* Install the [Extension Test Runner](https://marketplace.visualstudio.com/items?itemName=ms-vscode.extension-test-runner) +* Run the "watch" task via the **Tasks: Run Task** command. Make sure this is running, or tests might not be discovered. +* Open the Testing view from the activity bar and click the Run Test" button, or use the hotkey `Ctrl/Cmd + ; A` +* See the output of the test result in the Test Results view. +* Make changes to `src/test/extension.test.ts` or create new test files inside the `test` folder. + * The provided test runner will only consider files matching the name pattern `**.test.ts`. + * You can create folders inside the `test` folder to structure your tests any way you want. + +## Go further + +* [Follow UX guidelines](https://code.visualstudio.com/api/ux-guidelines/overview) to create extensions that seamlessly integrate with VS Code's native interface and patterns. +* Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). +* [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code extension marketplace. +* Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). +* Integrate to the [report issue](https://code.visualstudio.com/api/get-started/wrapping-up#issue-reporting) flow to get issue and feature requests reported by users.