-
-
Save PrashamTrivedi/de03e42cb5d53eb06d7089d107c50680 to your computer and use it in GitHub Desktop.
#!/usr/bin/env -S deno run -A | |
import { ensureFileSync } from "https://deno.land/std@0.224.0/fs/mod.ts"; | |
import { parseArgs } from "jsr:@std/cli/parse-args"; | |
import { Anthropic } from "npm:@anthropic-ai/sdk"; | |
import * as log from "jsr:@std/log"; | |
import { extname } from "jsr:@std/path"; | |
import { TextBlock } from "npm:@anthropic-ai/sdk"; | |
const aiPrompt = ` | |
You are an AI assistant specializing in Deno development, your task is to generate one-off Deno scripts in TypeScript for local execution. Follow these guidelines: | |
1. Use the latest Deno standards and best practices for local script development. | |
2. Prefer the Deno standard library (std) from JSR (https://jsr.io/@std) whenever possible. | |
3. When necessary, import additional dependencies from JSR, ESM, or NPM. | |
4. Write all scripts in TypeScript, leveraging Deno's built-in TypeScript support. | |
5. Use the following import syntax for standard library modules: | |
\`\`\`typescript | |
import { moduleName } from "jsr:@std/package-name"; | |
\`\`\` | |
6. For third-party dependencies, use the appropriate import syntax: | |
- JSR: \`import { module } from "jsr:@scope/package-name@^version";\` | |
- ESM: \`import { module } from "https://esm.sh/package-name@version";\` | |
- NPM: \`import { module } from "npm:package-name@version";\` | |
7. Utilize Deno's native TypeScript features without requiring separate compilation steps. | |
8. Include appropriate type annotations and interfaces. | |
9. Use async/await for asynchronous operations when applicable. | |
10. Implement error handling using try/catch blocks where necessary. | |
11. Add concise comments to explain complex logic or non-obvious code sections. | |
12. If the script requires permissions, include the necessary flags (e.g., --allow-read, --allow-net) in a comment at the top of the script. | |
13. For scripts that interact with files or network resources, use Deno's built-in APIs (e.g., Deno.readTextFile, fetch) instead of Node.js equivalents. | |
14. When working with paths, use the @std/path module for cross-platform compatibility. | |
15. For scripts that require configuration, consider using Deno's built-in support for environment variables or JSON configuration files. | |
16. Feel free to use unstable features (like Deno KV) if they provide significant benefits for the task at hand. When using unstable features, include a comment explaining why it's beneficial and mention the required --unstable flag. | |
17. Focus on creating self-contained scripts that can be run directly without additional setup or configuration. | |
18. If the script processes command-line arguments, use Deno's built-in \`Deno.args\` or a library like @std/flags for parsing. | |
19. For scripts that perform file system operations, prefer Deno's file system APIs (e.g., Deno.readTextFile, Deno.writeTextFile) over third-party alternatives. | |
20. If the script needs to store persistent data locally, consider using Deno KV (with the --unstable flag) or simple JSON files, depending on the complexity of the data. | |
21. For scripts that make HTTP requests, use the built-in fetch API or the @std/http module. | |
22. Always put proper shebang at the top of the script to indicate the interpreter for the file. | |
23. Use deno's logger for logging messages and debugging information. | |
24. Always generate the full script without leaving any part incomplete or missing. | |
25. Include proper error handling for file system operations and network requests | |
26. Add appropriate TypeScript types for all function parameters and return values | |
27. Use async iterators when processing streams or large datasets | |
28. Implement proper cleanup in case of script interruption | |
29. Add progress indicators for long-running operations | |
30. Include debug logging statements that can be enabled via environment variables or flags | |
31. When generating apps for CLI,make sure both long and short flags are supported | |
When generating the script: | |
1. Structure the code with clear separation of concerns | |
2. Include comprehensive error messages | |
3. Add proper type definitions | |
4. Include example usage in comments | |
5. Add proper cleanup handlers | |
6. Make sure the script is executable, and if the script is CLI verify that with printing help message | |
When generating the script, provide a brief explanation of its functionality, any notable features or design decisions, and instructions on how to run the script (including any required flags). | |
Add this explanation as comment in the script file. | |
If there are multiple ways to accomplish the task, choose the most straightforward and efficient approach for a local, one-off script. | |
Your output should only contain full TypeScript code for the Deno script without any yapping or any backticks.`; | |
const fileNameGeneratorPrompt = | |
`You will be given a prompt passed by the user to deno script generator prompt. Determine what the user wants to create, and give the appropriate file name to save the code to. Give me only file name and nothing else.`; | |
const args = parseArgs(Deno.args); | |
const configPath = args.config || args.c || "./config.json"; | |
const debug = args.debug || args.d || false; | |
const outputFile = args.output || args.o; | |
let anthropicKey = ""; | |
ensureFileSync(configPath); | |
const configContent = await Deno.readTextFile(configPath); | |
anthropicKey = JSON.parse(configContent).anthropicApiKey; | |
const anthropicClient = new Anthropic({ | |
apiKey: anthropicKey, | |
}); | |
const isTerminal = Deno.stdin.isTerminal(); | |
let promptFromPipe = ""; | |
if (!isTerminal) { | |
const buffer = new Uint8Array(1024); | |
const decoder = new TextDecoder(); | |
while (true) { | |
const nread = await Deno.stdin.read(buffer); | |
if (nread === null) break; | |
promptFromPipe += decoder.decode(buffer.subarray(0, nread)); | |
} | |
console.log({ promptFromPipe }); | |
} | |
let userQuery = args.query || args.q || args._.join(" "); | |
if (!userQuery && !promptFromPipe) { | |
console.error("Please provide a query to generate a Deno script."); | |
userQuery = prompt("Enter your query: "); | |
} | |
if (userQuery && promptFromPipe) { | |
userQuery = `${userQuery}<Input>${promptFromPipe}</Input>`; | |
} else { | |
userQuery = promptFromPipe; | |
} | |
let fileNameToGenerate = ""; | |
if (!outputFile) { | |
const fileNameGenerator = await anthropicClient.messages.create({ | |
model: "claude-3-haiku-20240307", | |
system: fileNameGeneratorPrompt, | |
messages: [ | |
{ | |
role: "user", | |
content: userQuery, | |
}, | |
], | |
max_tokens: 100, | |
temperature: 0, | |
}); | |
console.log({ fileNameGenerator }); | |
const textMessages = fileNameGenerator.content?.find((message) => | |
message.type === "text" | |
) as TextBlock | undefined; | |
if (textMessages) { | |
fileNameToGenerate = textMessages.text; | |
} | |
console.log({ fileNameToGenerate }); | |
} else { | |
fileNameToGenerate = outputFile; | |
} | |
if (!fileNameToGenerate) { | |
console.error("Output name wasn't provided or can't be inferred"); | |
Deno.exit(1); | |
} | |
if (extname(fileNameToGenerate) !== ".ts") { | |
console.error("The output file must have a .ts extension."); | |
Deno.exit(1); | |
} | |
if (debug) { | |
await log.setup({ | |
handlers: { | |
console: new log.ConsoleHandler("DEBUG", { | |
formatter: log.formatters.jsonFormatter, | |
}), | |
}, | |
loggers: { | |
default: { | |
level: "DEBUG", | |
handlers: ["console"], | |
}, | |
}, | |
}); | |
log.debug( | |
JSON.stringify({ userQuery, configPath, debug, fileNameToGenerate }), | |
); | |
} else { | |
console.log({ userQuery, fileNameToGenerate }); | |
} | |
const anthropicResponse = await anthropicClient.messages.stream({ | |
max_tokens: 8192, | |
model: "claude-3-5-sonnet-20241022", | |
system: aiPrompt, | |
messages: [ | |
{ | |
role: "user", | |
content: userQuery, | |
}, | |
], | |
temperature: 0, | |
}); | |
anthropicResponse.on("text", (text) => { | |
if (debug) { | |
log.debug(JSON.stringify({ event: "text", content: text })); | |
} else { | |
Deno.stdout.write(new TextEncoder().encode(text)); | |
} | |
}); | |
const finalScript = await anthropicResponse.finalMessage(); | |
if (debug) { | |
log.debug(JSON.stringify({ event: "finalMessage", content: finalScript })); | |
} | |
let scriptContent = ""; | |
for (const message of finalScript.content) { | |
if (message.type === "text") { | |
scriptContent += message.text; | |
} | |
} | |
await Deno.writeTextFile(fileNameToGenerate, scriptContent); | |
console.log(`\nScript has been saved to ${fileNameToGenerate}`); | |
const command = new Deno.Command("sudo", { | |
args: ["chmod", "+x", fileNameToGenerate], | |
stdout: "piped", | |
stderr: "piped", | |
}); | |
const { code, stdout, stderr } = await command.output(); | |
if (code === 0) { | |
console.log(`\n${fileNameToGenerate} has been made executable.`); | |
} | |
console.log(stdout); | |
console.error(stderr); |
Update for revision 2:
Allow to pipe anything to command and consider this as a prompt.
e,g, you can do cat instructions.md | denoScriptGenerator.ts -o yourFile.ts
or
begin echo "You have a code for a deno script which creates a server, reads config JSON stored on serverConfig.json, and handles routing and port mapping using this JSON. Create a deno script presenting htmx based UI to manages handling serverConfig.json. This script should do CRUD on the JSON. This script should run on port 5001."; echo "<HttpServerCode>"; cat httpServer.ts; echo "</HttpServerCode>"; end | denoScriptGenerator.ts -o configManagement.ts
Updated shebang to run this directly if you have deno installed.
Update for revision 3:
Mixing pipe and user prompt together. When both prompt and pipe content are there, pipe content will be provided in tag in the prompt at the end of the prompt.
Output file is no longer required, haiku should be able to generate output file name from prompt.
Like
cat ../CompanyCodeDir/Microservices.code-workspace | denoScriptGenerator.ts We need to create a webpage to
manage the workspace files provided in input tag, especially enabling and disabling folders. When disabled, they are part of disabledDir, when enabled they are part of folders. I also need ability to add new folder, if the folder is a ts project it should be added in folders as well as settings.eslint.workingDirectories otherwise we only need to add it in folders. Make sure the script has a configurable port. Uses htmx and tailwind for styling and theming
This will create a workspace-manager.ts file, read the code-workspace as piped one and then use this along with user prompt to generate the new file.
How to run it
Your prompt doesn't have to be in flag, You can start typing your prompt after
deno run -A denoScriptGenerator.ts -o yourFile.ts
and it will accept it as prompt.In case you need to pass the flag use
deno run -A denoScriptGenerator.ts -o yourFile.ts -q "query with spaces"
ordeno run -A denoScriptGenerator.ts -o yourFile.ts -query "query with spaces"
and it should run just file.Update: There are new ways to call this CLI, refer to comments below.