mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-28 07:16:53 +05:00
- Add onboarding module for pre-flight checks (Redis, Ollama, model, project) - Implement start command with TUI rendering and dependency injection - Implement init command for .ipuaro.json config file creation - Implement index command for standalone project indexing - Add CLI options: --auto-apply, --model, --help, --version - Register all 18 tools via tools-setup helper - Add 29 unit tests for CLI commands - Update CHANGELOG and ROADMAP for v0.15.0
163 lines
4.8 KiB
TypeScript
163 lines
4.8 KiB
TypeScript
/**
|
|
* Start command implementation.
|
|
* Launches the ipuaro TUI.
|
|
*/
|
|
|
|
import * as path from "node:path"
|
|
import * as readline from "node:readline"
|
|
import { render } from "ink"
|
|
import React from "react"
|
|
import { App, type AppDependencies } from "../../tui/App.js"
|
|
import { RedisClient } from "../../infrastructure/storage/RedisClient.js"
|
|
import { RedisStorage } from "../../infrastructure/storage/RedisStorage.js"
|
|
import { RedisSessionStorage } from "../../infrastructure/storage/RedisSessionStorage.js"
|
|
import { OllamaClient } from "../../infrastructure/llm/OllamaClient.js"
|
|
import { ToolRegistry } from "../../infrastructure/tools/registry.js"
|
|
import { generateProjectName } from "../../infrastructure/storage/schema.js"
|
|
import { type Config, DEFAULT_CONFIG } from "../../shared/constants/config.js"
|
|
import { checkModel, pullModel, runOnboarding } from "./onboarding.js"
|
|
import { registerAllTools } from "./tools-setup.js"
|
|
|
|
/**
|
|
* Options for start command.
|
|
*/
|
|
export interface StartOptions {
|
|
autoApply?: boolean
|
|
model?: string
|
|
}
|
|
|
|
/**
|
|
* Result of start command.
|
|
*/
|
|
export interface StartResult {
|
|
success: boolean
|
|
error?: string
|
|
}
|
|
|
|
/**
|
|
* Execute the start command.
|
|
*/
|
|
export async function executeStart(
|
|
projectPath: string,
|
|
options: StartOptions,
|
|
config: Config = DEFAULT_CONFIG,
|
|
): Promise<StartResult> {
|
|
const resolvedPath = path.resolve(projectPath)
|
|
const projectName = generateProjectName(resolvedPath)
|
|
|
|
const llmConfig = {
|
|
...config.llm,
|
|
model: options.model ?? config.llm.model,
|
|
}
|
|
|
|
console.warn("🔍 Running pre-flight checks...\n")
|
|
|
|
const onboardingResult = await runOnboarding({
|
|
redisConfig: config.redis,
|
|
llmConfig,
|
|
projectPath: resolvedPath,
|
|
})
|
|
|
|
for (const warning of onboardingResult.warnings) {
|
|
console.warn(`⚠️ ${warning}\n`)
|
|
}
|
|
|
|
if (!onboardingResult.success) {
|
|
for (const error of onboardingResult.errors) {
|
|
console.error(`❌ ${error}\n`)
|
|
}
|
|
|
|
if (!onboardingResult.modelOk && onboardingResult.ollamaOk) {
|
|
const shouldPull = await promptYesNo(
|
|
`Would you like to pull "${llmConfig.model}"? (y/n): `,
|
|
)
|
|
|
|
if (shouldPull) {
|
|
const pullResult = await pullModel(llmConfig, console.warn)
|
|
if (!pullResult.ok) {
|
|
console.error(`❌ ${pullResult.error ?? "Unknown error"}`)
|
|
return { success: false, error: pullResult.error }
|
|
}
|
|
|
|
const recheckModel = await checkModel(llmConfig)
|
|
if (!recheckModel.ok) {
|
|
console.error("❌ Model still not available after pull.")
|
|
return { success: false, error: "Model pull failed" }
|
|
}
|
|
} else {
|
|
return { success: false, error: "Model not available" }
|
|
}
|
|
} else {
|
|
return {
|
|
success: false,
|
|
error: onboardingResult.errors.join("\n"),
|
|
}
|
|
}
|
|
}
|
|
|
|
console.warn(`✅ All checks passed. Found ${String(onboardingResult.fileCount)} files.\n`)
|
|
console.warn("🚀 Starting ipuaro...\n")
|
|
|
|
const redisClient = new RedisClient(config.redis)
|
|
|
|
try {
|
|
await redisClient.connect()
|
|
|
|
const storage = new RedisStorage(redisClient, projectName)
|
|
const sessionStorage = new RedisSessionStorage(redisClient)
|
|
const llm = new OllamaClient(llmConfig)
|
|
const tools = new ToolRegistry()
|
|
|
|
registerAllTools(tools)
|
|
|
|
const deps: AppDependencies = {
|
|
storage,
|
|
sessionStorage,
|
|
llm,
|
|
tools,
|
|
}
|
|
|
|
const handleExit = (): void => {
|
|
void redisClient.disconnect()
|
|
}
|
|
|
|
const { waitUntilExit } = render(
|
|
React.createElement(App, {
|
|
projectPath: resolvedPath,
|
|
autoApply: options.autoApply ?? config.edit.autoApply,
|
|
deps,
|
|
onExit: handleExit,
|
|
}),
|
|
)
|
|
|
|
await waitUntilExit()
|
|
await redisClient.disconnect()
|
|
|
|
return { success: true }
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error)
|
|
console.error(`❌ Failed to start ipuaro: ${message}`)
|
|
await redisClient.disconnect()
|
|
return { success: false, error: message }
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Simple yes/no prompt for CLI.
|
|
*/
|
|
async function promptYesNo(question: string): Promise<boolean> {
|
|
return new Promise((resolve) => {
|
|
process.stdout.write(question)
|
|
|
|
const rl = readline.createInterface({
|
|
input: process.stdin,
|
|
output: process.stdout,
|
|
})
|
|
|
|
rl.once("line", (answer: string) => {
|
|
rl.close()
|
|
resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes")
|
|
})
|
|
})
|
|
}
|