Skip to content

Commit

Permalink
feat: migrarte init to clack (#65)
Browse files Browse the repository at this point in the history
* feat: migrate init to clack prompts

* fix

* fix empty input bug

* refactor
  • Loading branch information
mdjastrzebski authored Dec 6, 2024
1 parent 4b04eaf commit cf6f100
Show file tree
Hide file tree
Showing 24 changed files with 984 additions and 1,720 deletions.
5 changes: 1 addition & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,11 @@
"@ai-sdk/mistral": "^0.0.46",
"@ai-sdk/openai": "^0.0.72",
"@callstack/byorg-core": "0.3.1",
"@inkjs/ui": "^1.0.0",
"@clack/prompts": "^0.8.2",
"chalk": "^5.3.0",
"date-fns": "^3.6.0",
"dotenv": "^16.4.5",
"ink": "^4.4.1",
"ora": "^8.1.1",
"react": "^18.3.1",
"tiktoken": "^1.0.17",
"update-notifier": "^7.3.1",
"yargs": "^17.7.2",
Expand All @@ -64,7 +62,6 @@
"@rslib/core": "^0.0.16",
"@types/jest": "^29.5.14",
"@types/mock-fs": "^4.13.4",
"@types/react": "^18.3.12",
"@types/update-notifier": "^6.0.8",
"@vitest/coverage-v8": "^2.1.4",
"del-cli": "^5.1.0",
Expand Down
2 changes: 1 addition & 1 deletion src/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { command as chat } from './commands/chat/index.js';
import { command as init } from './commands/init/index.js';
import { command as init } from './commands/init.js';
import { checkForUpdates } from './update-notifier.js';

checkForUpdates();
Expand Down
File renamed without changes.
11 changes: 2 additions & 9 deletions src/commands/chat/commands.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import { CHATS_SAVE_DIRECTORY } from '../../file-utils.js';
import {
exit,
getVerbose,
output,
outputVerbose,
outputWarning,
setVerbose,
} from '../../output/index.js';
import { getVerbose, output, outputVerbose, outputWarning, setVerbose } from '../../output.js';
import { getProvider, getProviderConfig } from './providers.js';
import { messages } from './state.js';
import { saveConversation } from './utils.js';
import { exit, saveConversation } from './utils.js';

export function processChatCommand(input: string) {
if (!input.startsWith('/')) {
Expand Down
4 changes: 2 additions & 2 deletions src/commands/chat/format.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AssistantResponse } from '@callstack/byorg-core';
import { colorAssistant, colorVerbose } from '../../colors.js';
import { formatSpeed, formatTime } from '../../format.js';
import { colorAssistant, colorVerbose } from '../../output/colors.js';
import { getVerbose } from '../../output/index.js';
import { getVerbose } from '../../output.js';

export function formatResponse(response: AssistantResponse) {
let result = colorAssistant(response.content);
Expand Down
32 changes: 16 additions & 16 deletions src/commands/chat/index.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
import { Application, createApp, Message } from '@callstack/byorg-core';
import type { CommandModule } from 'yargs';
import { parseConfigFile } from '../../config-file.js';
import { colorAssistant } from '../../output/colors.js';
import {
clearCurrentLine,
output,
outputError,
readUserInput,
setInterruptHandler,
setVerbose,
} from '../../output/index.js';
import { checkIfConfigExists, parseConfigFile } from '../../config-file.js';
import { output, outputError, setVerbose } from '../../output.js';
import { run as runInit } from '../init.js';
import { colorAssistant } from '../../colors.js';
import { initInput, readUserInput, setInterruptHandler } from './input.js';
import { processChatCommand } from './commands.js';
import { cliOptions, type CliOptions } from './cli-options.js';
import { formatResponse } from './format.js';
import { getProvider, getProviderConfig, initProvider } from './providers.js';
import { spinnerStart, spinnerStop, spinnerUpdate } from './spinner.js';
import { messages } from './state.js';
import { texts } from './texts.js';
import { exit } from './utils.js';

export const command: CommandModule<{}, CliOptions> = {
command: ['chat', '$0'],
Expand All @@ -25,10 +21,15 @@ export const command: CommandModule<{}, CliOptions> = {
handler: (args) => run(args._.join(' '), args),
};

let shouldExit = false;

async function run(initialPrompt: string, options: CliOptions) {
const hasConfig = checkIfConfigExists();
if (!hasConfig) {
await runInit();
return;
}

setVerbose(options.verbose ?? false);
initInput();

try {
const configFile = parseConfigFile();
Expand All @@ -44,17 +45,16 @@ async function run(initialPrompt: string, options: CliOptions) {

setInterruptHandler(() => {
spinnerStop();
clearCurrentLine();
console.log('\nBye...');
shouldExit = true;
exit();
});

if (messages.length > 0) {
messages.forEach((m) => outputMessage(m));
await processMessages(app, messages);
}

while (!shouldExit) {
// eslint-disable-next-line no-constant-condition
while (true) {
const userMessage = await readUserInput();
if (processChatCommand(userMessage)) {
continue;
Expand Down
40 changes: 40 additions & 0 deletions src/commands/chat/input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import readline from 'readline';
import { texts } from './texts.js';

let rl: readline.promises.Interface;

export function initInput() {
rl = readline.promises.createInterface({
input: process.stdin,
output: process.stdout,
});

rl.on('SIGINT', () => {
if (interruptHandler) {
interruptHandler();
}

rl.close();
});
}

export function closeInput() {
readline.clearLine(process.stdout, 0);
readline.cursorTo(process.stdout, 0);
rl.close();
}

export async function readUserInput(): Promise<string> {
let answer = '';
while (!answer.trim()) {
answer = await rl.question(`${texts.userLabel} `);
}

return answer;
}

let interruptHandler: (() => void) | undefined;

export function setInterruptHandler(handler: () => void) {
interruptHandler = handler;
}
2 changes: 1 addition & 1 deletion src/commands/chat/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import {
type Provider,
type ProviderName,
} from '../../engine/providers/provider.js';
import { output, outputVerbose, outputWarning } from '../../output/index.js';
import openAi from '../../engine/providers/open-ai.js';
import anthropic from '../../engine/providers/anthropic.js';
import perplexity from '../../engine/providers/perplexity.js';
import mistral from '../../engine/providers/mistral.js';
import { output, outputVerbose, outputWarning } from '../../output.js';
import { CliOptions } from './cli-options.js';
import { filterOutApiKey, handleInputFile } from './utils.js';

Expand Down
8 changes: 8 additions & 0 deletions src/commands/chat/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,15 @@ import {
getDefaultFilename,
getUniqueFilename,
} from '../../file-utils.js';
import { output } from '../../output.js';
import { texts } from './texts.js';
import { closeInput } from './input.js';

export function exit() {
closeInput();
output('\nBye...');
process.exit(0);
}

interface HandleInputFileResult {
systemPrompt: string;
Expand Down
68 changes: 68 additions & 0 deletions src/commands/init.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { intro, confirm, log, outro, select, isCancel, text, note } from '@clack/prompts';
import type { CommandModule } from 'yargs';
import { checkIfConfigExists, writeConfigFile } from '../config-file.js';
import { ProviderName, providers, providersMap } from '../engine/providers/provider.js';
import { outputError } from '../output.js';

export const command: CommandModule<{}> = {
command: ['init'],
describe: 'Basic configuration of AI CLI.',
handler: () => run(),
};

export async function run() {
try {
intro('Welcome to AI CLI!');

const hasConfig = checkIfConfigExists();
if (hasConfig) {
const overwrite = await confirm({
message: 'Existing "~/.airc.json" file found, do you want to overwrite it?',
initialValue: false,
});
if (isCancel(overwrite)) return;

if (!overwrite) {
outro('Nothing to do. Exiting.');
return;
}
}

log.message("Let's set you up quickly.");

const provider = await select({
message: 'Which AI provider would you like to use:',
options: providers.map((p) => ({ label: p.label, value: p.name })),
});
if (isCancel(provider)) return;

const apiKey = await text({
message: `Paste ${providersMap[provider].label} API key here:`,
placeholder: `Get your key at ${providersMap[provider].apiKeyUrl}`,
validate: (value) => {
if (!value) {
return 'API key is required.';
}
},
});
if (isCancel(apiKey)) return;

writeConfig(provider, apiKey);
log.step('Written your settings into "~/.airc.json" file.');

note('$ ai "Tell me a useful productivity hack"', 'You can now use AI CLI');

outro('Done!');
} catch (error) {
outputError(error);
process.exit(1);
}
}

function writeConfig(provider: ProviderName, apiKey: string) {
writeConfigFile({
providers: {
[provider]: { apiKey },
},
});
}
28 changes: 0 additions & 28 deletions src/commands/init/index.tsx

This file was deleted.

41 changes: 0 additions & 41 deletions src/commands/init/ui/ConfirmStep.tsx

This file was deleted.

Loading

0 comments on commit cf6f100

Please sign in to comment.