diff --git a/README.md b/README.md index d3b2533..46ca7c2 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ export NVIDIA_API_KEY=nvapi-... | Doc | Contents | |-----|----------| +| **[HOW_TO_USE.md](docs/HOW_TO_USE.md)** | **Detailed premium startup, commands, and layouts guide** | | **[GUIDE.md](docs/GUIDE.md)** | Full command reference + working playbooks + diagrams | | [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | System design | | [docs/SESSIONS.md](docs/SESSIONS.md) | Session memory | diff --git a/docs/HOW_TO_USE.md b/docs/HOW_TO_USE.md new file mode 100644 index 0000000..d4e717e --- /dev/null +++ b/docs/HOW_TO_USE.md @@ -0,0 +1,47 @@ +# How to Use ArrowCode + +Welcome to **ArrowCode**, a premium, high-performance multi-agent terminal coding harness. ArrowCode cleanly overtakes your terminal screen, offering an immersive, glitch-free interactive development environment tailored to modern engineering workflows. + +--- + +## Immersive Control Loop + +When you open ArrowCode, it takes over the terminal window, hiding standard shell scrollback history to keep your focus perfectly locked. On exiting, your previous shell canvas is fully restored. + +1. **The Startup Screen**: + - Offers cursor-based navigation (Arrow keys + Enter) to start a new worktree, resume a session, view history, or quit. + - Includes standard system shortcuts: + - `ctrl+w`: Start a new session / worktree. + - `ctrl+s`: Resume your previous session. + - `ctrl+q`: Gracefully quit the application and restore terminal. + +2. **Core Operational Loop**: + - **`/plan [goal]`**: Start drafting an execution strategy for your target task. + - **Clarifying Questions**: ArrowCode's Orchestrator raises questions to eliminate ambiguity before touching any code. + - **`/confirm`**: Approves the compiled plan and kicks off parallel execution across main agents & spawnable worker swarms. + - **`/accept`**: Finalizes the changes, persists the session state, and cleanly logs out. + +--- + +## Commands Catalog + +| Command | Action / Explanation | +|---|---| +| `/help` | View help details and interactive command overlays. | +| `/plan [goal]` | Kick off the planning phase for a feature or bugfix. | +| `/confirm` | Approve the plan and begin automatic code construction and tests. | +| `/accept` | Conclude and save the session successfully. | +| `/reject [note]` | Decline the proposed changes and ask the agents to refine the result. | +| `/stop` | Freeze all currently running background execution runs immediately. | +| `/undo` | Walk back to the previous workspace checkpoint safely. | +| `/yolo` | Toggle automatic execution approval for all file edits and bash tools. | +| `/settings` | Open the full-screen terminal configuration dashboard. | +| `/exit` | Exit ArrowCode and cleanly restore your standard shell history. | + +--- + +## Layout Modes + +ArrowCode supports two beautiful premium layout experiences: +- **Classic Mode (Default)**: A clean minimalist chat pane showing elegant streaming conversation with a low-noise single-line status representing active agents (**ORCH**, **FE**, **BE**, **QA**). +- **Dashboard Mode**: An advanced 2x2 terminal overlay showing live agent terminals, a swarm map tree, plan checklists, active file tree, and code diff highlights. Run `/swarm` (or `/layout`) to toggle layouts instantly. diff --git a/src/brand/banner.ts b/src/brand/banner.ts index 67c53d7..cfb8ca9 100644 --- a/src/brand/banner.ts +++ b/src/brand/banner.ts @@ -19,6 +19,13 @@ export const BANNER_COMPACT = ` `.replace(/^\n/, ""); /** Clean line-art mark for small terminals / headers */ +export const PREMIUM_MINIMAL_ASCII = ` + ▞▀▖ + ▚▄▘▛▀▖▛▀▖▞▀▖▌ ▌▞▀▖ + ▞ ▖▙▄▘▙▄▘▌ ▌▐▐ ▌▌ ▌ + ▚▄▘▛ ▘▛ ▘▚▄▘ ▘ ▘▚▄▘ +`.trim(); + export const MARK = ` /\\ / \\ ARROWCODE diff --git a/src/index.ts b/src/index.ts index 5723cb3..b18ffc2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -259,123 +259,41 @@ async function main() { console.log("\n[metrics]", harness.metricsLine()); process.exit(0); } else { - // CLI Interactive Session (like Claude Code) is now the default! - const readline = await import("node:readline"); - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - - console.log("\n================================================================"); - console.log(" /\\"); - console.log(" / \\ ARROWCODE Interactive CLI"); - console.log(" / /\\ \\ swarm coding harness"); - console.log(" /______\\ ORCH · FE · BE · QA"); - console.log("================================================================\n"); - console.log(" Welcome! You are in the interactive CLI mode (similar to Claude Code)."); - console.log(" - Type standard prompt to chat with the agents."); - console.log(" - Use /plan to propose a new plan."); - console.log(" - Use /add to attach a file to the context."); - console.log(" - Use /swarm to launch the fullscreen dashboard TUI."); - console.log(" - Use /confirm to approve and execute the current plan."); - console.log(" - Use /accept to finish and save changes."); - console.log(" - Use /help to see all available commands."); - console.log(" - Type /exit or /quit to quit.\n"); - - const attachedFiles: { path: string; content: string }[] = []; - - const promptUser = () => { - rl.question("arrowcode> ", async (answer) => { - const line = answer.trim(); - if (!line) { - promptUser(); - return; - } - if (line === "/exit" || line === "/quit") { - rl.close(); - process.exit(0); - } - - let cmdLine = line; - if (cmdLine.startsWith(".")) { - cmdLine = "/" + cmdLine.slice(1); - } + // Enter alternate screen buffer to cleanly overtake the whole terminal canvas + // and hide scrollback history while running. + process.stdout.write("\x1b[?1049h\x1b[H"); - if (cmdLine === "/swarm") { - rl.close(); - console.log("\nLaunching Multi-Agent Swarm TUI Dashboard..."); - const { render } = await import("ink"); - const { App } = await import("./tui/App"); - const { waitUntilExit } = render( - React.createElement(App, { - harness, - config: cfg, - initialPrompt: undefined, - }), - { exitOnCtrlC: true }, - ); - await waitUntilExit(); - process.exit(0); - } - - if (cmdLine.startsWith("/add ")) { - const filePath = cmdLine.slice(5).trim(); - try { - const absolutePath = harness.workspace.resolve(filePath, { mustExist: true }); - const fs = await import("node:fs"); - const content = fs.readFileSync(absolutePath, "utf8"); - const lineCount = content.split("\n").length; - attachedFiles.push({ path: filePath, content }); - console.log(`[system] Attached file ${filePath} (${lineCount} lines)`); - } catch (err: any) { - console.log(`[system] Error: Failed to attach file: ${err.message}`); - } - promptUser(); - return; - } - - if (cmdLine.startsWith("/")) { - // Dispatch command - const [cmd, ..._rest] = cmdLine.slice(1).split(/\s+/); - const { dispatchCommand } = await import("./commands/registry"); + const exitAlternateScreen = () => { + process.stdout.write("\x1b[?1049l\x1b[?25h"); + }; - let modifiedCmdLine = cmdLine; - if (cmd === "plan" && attachedFiles.length > 0) { - const attachmentBlock = attachedFiles - .map((f) => `=== ATTACHED FILE: ${f.path} ===\n${f.content}\n===============================`) - .join("\n\n"); - modifiedCmdLine = `/plan ${attachmentBlock}\n\nUser Request:\n${cmdLine.slice(5).trim()}`; - attachedFiles.length = 0; - } + process.on("exit", exitAlternateScreen); + process.on("SIGINT", () => { + exitAlternateScreen(); + process.exit(130); + }); + process.on("uncaughtException", (err) => { + exitAlternateScreen(); + console.error(err); + process.exit(1); + }); - const res = await dispatchCommand(modifiedCmdLine, { - harness, - setYolo: () => {}, - getYolo: () => false, - }); - if (res.type === "exit") { - rl.close(); - process.exit(0); - } - if (res && "message" in res && res.message) { - console.log(`[system] ${res.message}`); - } - } else { - let payload = line; - if (attachedFiles.length > 0) { - const attachmentBlock = attachedFiles - .map((f) => `=== ATTACHED FILE: ${f.path} ===\n${f.content}\n===============================`) - .join("\n\n"); - payload = `${attachmentBlock}\n\nUser Prompt:\n${line}`; - attachedFiles.length = 0; - } - await harness.chat(payload); - } - promptUser(); - }); - }; - promptUser(); - return; + try { + const { render } = await import("ink"); + const { App } = await import("./tui/App"); + const { waitUntilExit } = render( + React.createElement(App, { + harness, + config: cfg, + initialPrompt: undefined, + }), + { exitOnCtrlC: true }, + ); + await waitUntilExit(); + } finally { + exitAlternateScreen(); + } + process.exit(0); } } diff --git a/src/tui/App.tsx b/src/tui/App.tsx index 3d2a732..008871c 100644 --- a/src/tui/App.tsx +++ b/src/tui/App.tsx @@ -121,6 +121,8 @@ function emptyAgent(id: AgentId): AgentPaneModel { }; } +import { PREMIUM_MINIMAL_ASCII } from "../brand/banner"; + export function App(props: { harness: Harness; config: ArrowConfig; @@ -132,6 +134,11 @@ export function App(props: { const { stdout } = useStdout(); const [cols, setCols] = useState(stdout?.columns || 120); const [rows, setRows] = useState(stdout?.rows || 40); + + // Premium Minimal Home View State matching the reference screenshot! + const [isHomeActive, setIsHomeActive] = useState(true); + const [selectedHomeIndex, setSelectedHomeIndex] = useState(0); + const [layoutMode, setLayoutMode] = useState("classic"); // Default is simple/classic chat interface! const [attachedFiles, setAttachedFiles] = useState<{ path: string; content: string }[]>([]); const [chatLines, setChatLines] = useState<{ id: string; sender: string; text: string; color?: string }[]>([ @@ -400,6 +407,58 @@ export function App(props: { setOverlay("none"); return; } + + if (isHomeActive) { + // Handle keys on the beautiful Startup Menu + if (key.upArrow) { + setSelectedHomeIndex((prev) => (prev > 0 ? prev - 1 : 3)); + return; + } + if (key.downArrow) { + setSelectedHomeIndex((prev) => (prev < 3 ? prev + 1 : 0)); + return; + } + if (key.return) { + if (selectedHomeIndex === 0) { + // New session / task + setIsHomeActive(false); + } else if (selectedHomeIndex === 1) { + // Resume session + setIsHomeActive(false); + // Try loading last session automatically + const ls = harness.sessionList(); + const match = ls.match(/Session ID: ([^\s]+)/); + if (match && match[1]) { + harness.sessionLoad(match[1]); + } + } else if (selectedHomeIndex === 2) { + // Changelog + setOverlay("replay"); + } else if (selectedHomeIndex === 3) { + // Quit + exit(); + } + return; + } + if (key.ctrl && input === "w") { + setIsHomeActive(false); + return; + } + if (key.ctrl && input === "s") { + setIsHomeActive(false); + const ls = harness.sessionList(); + const match = ls.match(/Session ID: ([^\s]+)/); + if (match && match[1]) { + harness.sessionLoad(match[1]); + } + return; + } + if (key.ctrl && input === "q") { + exit(); + return; + } + } + if (key.tab && overlay === "none" && filePaths.length) { setFileFocus((i) => { const n = (i + 1) % filePaths.length; @@ -427,6 +486,10 @@ export function App(props: { line = "/" + line.slice(1); } + if (isHomeActive) { + setIsHomeActive(false); + } + // Save to user chat log too setChatLines((prev) => [ ...prev.slice(-100), @@ -631,6 +694,27 @@ export function App(props: { const panes = useMemo(() => AGENT_ORDER.map((id) => agents[id]), [agents]); + // Determine agent status lights for premium minimalistic display + const renderAgentStatusTags = () => { + return ( + + {panes.map((p) => { + let color = "gray"; + if (p.status === "thinking") color = "cyan"; + else if (p.status === "tool") color = "yellow"; + else if (p.status === "waiting") color = "magenta"; + else if (p.status === "done") color = "green"; + return ( + + {p.id.toUpperCase()}: + {p.status.toUpperCase()} + + ); + })} + + ); + }; + if (overlay === "settings") { return ( @@ -768,6 +852,76 @@ export function App(props: { ); } + if (isHomeActive) { + // Beautiful Startup screen matching the reference image from the prompt! + return ( + + + + {PREMIUM_MINIMAL_ASCII} + + + {" swarm coding harness"} + + + + + {[ + { label: "New worktree", shortcut: "ctrl+w" }, + { label: "Resume session", shortcut: "ctrl+s" }, + { label: "Changelog", shortcut: "" }, + { label: "Quit", shortcut: "ctrl+q" }, + ].map((item, idx) => { + const isSelected = selectedHomeIndex === idx; + return ( + + + {isSelected ? "❯ " : " "} + {item.label} + + + {item.shortcut} + + + ); + })} + + + + + {`ArrowCode 1.0 is here, try it out for free for a limited time!`} + + + {"[Ready for prompt input below] or use Arrow Keys + Enter"} + + + + + + Tip: Use standard CLI prompts to start coding immediately. + + void onSubmit(v)} + placeholder="Type prompt to start a new worktree / session" + /> + + + ArrowCode 1.0.0 Beta + + + + + ); + } + return (
- {/* FULL SCREEN CHAT PANEL: Beautiful classic scrollable Chat stream */} + {/* Full Screen Chat Panel: Beautiful classic scrollable Chat stream with status tags */} - - {chatLines.slice(-10).map((line) => ( + + {chatLines.slice(-6).map((line) => ( {`[${line.sender}]`} @@ -858,6 +1012,7 @@ export function App(props: { ))} + {renderAgentStatusTags()} )}