feat: core agent tools (fs, search, shell)

This commit is contained in:
Dave
2025-12-24 16:59:14 +00:00
parent 54810631be
commit 76e03bc1a2
19 changed files with 825 additions and 52 deletions

View File

@@ -1,49 +1,77 @@
import { useState } from "react";
import reactLogo from "./assets/react.svg";
import { invoke } from "@tauri-apps/api/core";
import { open } from "@tauri-apps/plugin-dialog";
import { ToolTester } from "./components/ToolTester";
import "./App.css";
function App() {
const [greetMsg, setGreetMsg] = useState("");
const [name, setName] = useState("");
const [projectPath, setProjectPath] = useState<string | null>(null);
const [errorMsg, setErrorMsg] = useState<string | null>(null);
async function greet() {
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
setGreetMsg(await invoke("greet", { name }));
async function selectProject() {
try {
setErrorMsg(null);
// Open native folder picker
const selected = await open({
directory: true,
multiple: false,
});
if (selected === null) {
// User cancelled selection
return;
}
// Invoke backend command to verify and set state
// Note: invoke argument names must match Rust function args
const confirmedPath = await invoke<string>("open_project", {
path: selected,
});
setProjectPath(confirmedPath);
} catch (e) {
console.error(e);
setErrorMsg(
typeof e === "string" ? e : "An error occurred opening the project.",
);
}
}
return (
<main className="container">
<h1>Welcome to Tauri + React</h1>
<h1>AI Code Assistant</h1>
<div className="row">
<a href="https://vite.dev" target="_blank">
<img src="/vite.svg" className="logo vite" alt="Vite logo" />
</a>
<a href="https://tauri.app" target="_blank">
<img src="/tauri.svg" className="logo tauri" alt="Tauri logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<p>Click on the Tauri, Vite, and React logos to learn more.</p>
{!projectPath ? (
<div className="selection-screen">
<p>
Please select a project folder to start the Story-Driven Spec
Workflow.
</p>
<button onClick={selectProject}>Open Project Directory</button>
</div>
) : (
<div className="workspace">
<div
className="toolbar"
style={{
padding: "10px",
background: "#f0f0f0",
borderRadius: "4px",
color: "#333",
}}
>
<strong>Active Project:</strong> {projectPath}
</div>
<hr style={{ margin: "20px 0" }} />
<p>Project loaded successfully.</p>
<ToolTester />
</div>
)}
<form
className="row"
onSubmit={(e) => {
e.preventDefault();
greet();
}}
>
<input
id="greet-input"
onChange={(e) => setName(e.currentTarget.value)}
placeholder="Enter a name..."
/>
<button type="submit">Greet</button>
</form>
<p>{greetMsg}</p>
{errorMsg && (
<div className="error-message" style={{ marginTop: "20px" }}>
<p style={{ color: "red" }}>Error: {errorMsg}</p>
</div>
)}
</main>
);
}

View File

@@ -0,0 +1,69 @@
import { useState } from "react";
import { invoke } from "@tauri-apps/api/core";
export function ToolTester() {
const [output, setOutput] = useState<string>("Ready.");
const runCommand = async (name: string, args: Record<string, unknown>) => {
setOutput(`Running ${name}...`);
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const res = await invoke(name, args);
setOutput(JSON.stringify(res, null, 2));
} catch (e) {
setOutput(`Error: ${e}`);
}
};
return (
<div
style={{ marginTop: "20px", border: "1px solid #ccc", padding: "10px" }}
>
<h3>Tool Tester</h3>
<div style={{ display: "flex", gap: "10px", flexWrap: "wrap" }}>
<button onClick={() => runCommand("list_directory", { path: "." })}>
List Root
</button>
<button
onClick={() =>
runCommand("read_file", { path: ".living_spec/README.md" })
}
>
Read Spec
</button>
<button onClick={() => runCommand("search_files", { query: "Story" })}>
Search "Story"
</button>
<button
onClick={() =>
runCommand("exec_shell", { command: "ls", args: ["-F"] })
}
>
Shell: ls -F
</button>
<button
onClick={() =>
runCommand("exec_shell", { command: "git", args: ["status"] })
}
>
Shell: git status
</button>
</div>
<pre
style={{
marginTop: "10px",
background: "#333",
color: "#fff",
padding: "10px",
borderRadius: "5px",
overflowX: "auto",
textAlign: "left",
fontSize: "12px",
}}
>
{output}
</pre>
</div>
);
}

15
src/types.ts Normal file
View File

@@ -0,0 +1,15 @@
export interface FileEntry {
name: string;
kind: "file" | "dir";
}
export interface SearchResult {
path: string;
matches: number;
}
export interface CommandOutput {
stdout: string;
stderr: string;
exit_code: number;
}