Source Code
Example code from "@MyAskIO/cli"
- prompt.ts on Github
Source code file for constructing AI prompts for the MyAsk Website
import { Command, Flags } from "@oclif/core";
import axios, { AxiosError } from "axios";
import inquirer from "inquirer";
import cliSpinners from "cli-spinners"; // Animated status indicator
import ora from "ora"; // Loading spinner
import { loadConfig } from "../utils/config.js";
import { formatCodeBlocks } from "../utils/codeFormatter.js";
export default class Ask extends Command {
static description = "Send a prompt to MyAsk API and retrieve a response.";
static flags = {
prompt: Flags.string({ char: "p", description: "Prompt to send" }),
inputFile: Flags.string({ char: "F", description: "Path to file(s) containing the question input", multiple: true }),
contextIds: Flags.string({ char: "c", description: "Comma-separated list of context IDs", multiple: true }),
};
static examples = [
'<%= config.bin %> <%= command.id %> -p "How do I add ActiveAdmin to a Rails 7 app?"',
'<%= config.bin %> <%= command.id %> -p "Refactor this file" -F path/to/src/file.ts',
'<%= config.bin %> <%= command.id %> -F path/to/input.txt',
'<%= config.bin %> <%= command.id %> -p "Help me combine these files:" -F path/to/file1.ts -F path/to/file2.ts',
'<%= config.bin %> <%= command.id %> -p "Analyze this code" -c 123 -c 456',
];
async run() {
const { flags } = await this.parse(Ask);
const { token: authToken, host: apiHost } = loadConfig().api;
const { id: projectId } = loadConfig().project;
if (!authToken) {
this.error("Missing MyAsk API token. Set it using `myask config -k YOUR_AUTH_TOKEN`.");
}
if (!projectId) {
this.error("MyAsk Project Not Set. Set it using `myask api:project:set`.");
}
let content = await this.getInitialQuestion(flags);
let contextIds: number[] = this.parseContextIds(flags.contextIds);
while (true) {
try {
const questionId = await this.submitQuestion(apiHost, authToken, projectId, content, contextIds);
if (!questionId) return;
contextIds.push(questionId); // Append new question ID to context
const response = await this.pollForResponse(apiHost, authToken, questionId);
this.log("\nš” MyAsk API Response:");
this.log(formatCodeBlocks(response));
const { isFollowUp } = await inquirer.prompt([
{ type: "confirm", name: "isFollowUp", message: "Would you like to ask a follow-up question?", default: false },
]);
if (!isFollowUp) break;
const { followUpQuestion } = await inquirer.prompt([
{ type: "input", name: "followUpQuestion", message: "Enter your follow-up question:" },
]);
content = followUpQuestion.trim();
} catch (error) {
this.error(`${error instanceof Error ? error.message : "Unknown error"}`);
}
}
}
private async submitQuestion(apiHost: string, authToken: string, projectId: number, content: string, contextIds: number[]): Promise<number | null> {
try {
const response = await axios.post(
`${apiHost}/api/questions`,
{ question: { project_id: projectId, content, context_ids: contextIds } },
{ headers: { Authorization: `Bearer ${authToken}`, "Content-Type": "application/json" } }
);
return response.data?.id || null;
} catch (error) {
const errorMessage = this.extractErrorMessage(error);
this.error(`ā ${errorMessage}`);
return null;
}
}
private async pollForResponse(apiHost: string, authToken: string, questionId: number): Promise<string> {
const spinner = ora({ text: "š Waiting for response from MyAsk API...", spinner: cliSpinners.dots }).start();
const maxRetries = 50;
let attempt = 0;
while (attempt < maxRetries) {
try {
await new Promise((resolve) => setTimeout(resolve, 3000));
const response = await axios.get(`${apiHost}/api/responses/${questionId}`, {
headers: { Authorization: `Bearer ${authToken}` },
});
if (response.data?.content) {
spinner.succeed("ā
Response received!");
return response.data.content;
}
} catch (error: any) {
const errorMessage = this.extractErrorMessage(error);
if (error.response?.data?.errors?.includes("Response doesn't exist")) {
spinner.text = `ā³ Still waiting for MyAsk API response... [Attempt: ${attempt + 1}/10]`;
} else {
spinner.fail(`ā Error fetching response: ${errorMessage}`);
throw new Error(errorMessage);
}
}
attempt++;
}
spinner.fail("ā Timed out waiting for MyAsk API response.");
throw new Error("No response received from MyAsk API after multiple attempts.");
}
private extractErrorMessage(error: unknown): string {
if (axios.isAxiosError(error)) {
return error.response?.data?.messages || error.response?.data?.error || error.message || "Unknown API error";
}
return error instanceof Error ? error.message : "An unknown error occurred";
}
private async getInitialQuestion(flags: any): Promise<string> {
let prompt = flags.prompt ? flags.prompt.trim() : "";
let fileContents = [];
if (flags.inputFile && flags.inputFile.length > 0) {
const fs = await import("fs/promises");
for (const filePath of flags.inputFile) {
try {
const content = (await fs.readFile(filePath, "utf-8")).trim();
fileContents.push(`š **File: ${filePath}**\n${content}`);
} catch (error) {
this.error(`ā Failed to read input file: ${filePath}`);
}
}
}
let combinedQuestion = prompt;
if (fileContents.length > 0) {
combinedQuestion += `\n\n---\n${fileContents.join("\n\n---\n")}`;
}
if (combinedQuestion.trim()) return combinedQuestion;
const { userPrompt } = await inquirer.prompt([
{ type: "input", name: "userPrompt", message: "Please enter a prompt:" },
]);
return userPrompt.trim();
}
private parseContextIds(contextIds: string[] | undefined): number[] {
if (!contextIds || contextIds.length === 0) return [];
try {
return contextIds.flatMap(id => id.split(",")).map(id => parseInt(id.trim(), 10)).filter(id => !isNaN(id));
} catch {
this.error("ā Invalid context IDs provided. Please ensure they are numbers.");
return [];
}
}
}