Add clear command, fix CLI shell completion/history, add launch script

- New DELETE /api/projects endpoint and GraphRepository.clearAll() wipe
  the whole Neo4j database (schema preserved).
- New CLI 'clear' command with confirmation prompt (-y/--yes to skip).
- Fix interactive shell: compile the picocli SystemCompleter before use
  (was throwing IllegalStateException on Tab), and persist the actual
  LineReader history instead of an unattached DefaultHistory instance.
- Add ac-cli/agenticcode.sh to launch the interactive shell from the
  built jar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ingo Schnabel
2026-06-14 13:33:53 +02:00
parent fd73cf6279
commit 0b3f664b3f
8 changed files with 94 additions and 4 deletions

14
ac-cli/agenticcode.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# Starts the AgenticCode interactive shell using the jar built in target/.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
JAR="$SCRIPT_DIR/target/ac-cli-1.0.0-SNAPSHOT.jar"
if [[ ! -f "$JAR" ]]; then
echo "Jar not found: $JAR" >&2
echo "Build it first with: mvn -pl ac-cli -am package" >&2
exit 1
fi
exec java -jar "$JAR" "$@"

View File

@@ -25,7 +25,8 @@ import java.util.concurrent.Callable;
CallersCommand.class,
CalleesCommand.class,
DbAccessesCommand.class,
SearchIdentifierCommand.class
SearchIdentifierCommand.class,
ClearCommand.class
}
)
public final class AgenticCodeCli implements Callable<Integer> {

View File

@@ -0,0 +1,28 @@
package com.agenticcode.cli;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
/**
* Deletes all projects and data, leaving an empty Neo4j database.
*/
@Command(name = "clear", mixinStandardHelpOptions = true, description = "Delete ALL projects and data (empties the database)")
final class ClearCommand extends AbstractApiCommand {
@Option(names = {"-y", "--yes"}, description = "Skip the confirmation prompt")
boolean yes;
@Override
public Integer call() throws Exception {
if (!yes) {
System.out.print("Delete ALL projects and data? This cannot be undone. (y/N) ");
System.out.flush();
String answer = new java.io.BufferedReader(new java.io.InputStreamReader(System.in)).readLine();
if (answer == null || !answer.trim().equalsIgnoreCase("y")) {
System.out.println("Aborted.");
return 0;
}
}
return printResponse(apiClient().delete("/api/projects"));
}
}

View File

@@ -2,7 +2,7 @@ package com.agenticcode.cli;
import org.jline.reader.*;
import org.jline.reader.impl.DefaultParser;
import org.jline.reader.impl.history.DefaultHistory;
import org.jline.reader.impl.completer.SystemCompleter;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import picocli.CommandLine;
@@ -27,14 +27,16 @@ final class InteractiveShell {
int run() throws IOException {
try (Terminal terminal = TerminalBuilder.builder().build()) {
PicocliCommands picocliCommands = new PicocliCommands(commandLine);
SystemCompleter completer = picocliCommands.compileCompleters();
completer.compile();
LineReader reader = LineReaderBuilder.builder()
.terminal(terminal)
.parser(new DefaultParser())
.completer(picocliCommands.compileCompleters())
.completer(completer)
.appName("agenticcode")
.variable(LineReader.HISTORY_FILE, HISTORY_FILE)
.build();
History history = new DefaultHistory(reader);
History history = reader.getHistory();
terminal.writer().println("AgenticCode interactive shell. Type 'help' for commands, 'exit' to quit.");
String serverUrl = CliConfig.resolveServerUrl(System.getProperty(AbstractApiCommand.SERVER_URL_PROPERTY));

View File

@@ -60,6 +60,11 @@ public class ProjectResource {
});
}
@DELETE
public Uni<Response> clearAll() {
return graphRepository.clearAll().replaceWith(Response.noContent().build());
}
@DELETE
@Path("/{project}")
public Uni<Response> delete(@PathParam("project") String project) {

View File

@@ -74,6 +74,29 @@ class ProjectResourceIT {
.statusCode(204);
}
@Test
void clearAllRemovesAllProjects() {
String project = "clear-project";
given()
.contentType("application/json")
.body(new ProjectResource.ProjectRequest(null))
.when().post("/api/projects/" + project)
.then()
.statusCode(201);
given()
.when().delete("/api/projects")
.then()
.statusCode(204);
given()
.when().get("/api/projects")
.then()
.statusCode(200)
.body("name", org.hamcrest.Matchers.not(org.hamcrest.Matchers.hasItem(project)));
}
@Test
void updateAndDeleteUnknownProjectReturnNotFound() {
String project = "missing-project";

View File

@@ -65,6 +65,10 @@ public final class CypherQueries {
RETURN p.name AS name, p.description AS description
ORDER BY p.name
""";
public static final String CLEAR_ALL = """
MATCH (n)
DETACH DELETE n
""";
/**
* Schema statements (indexes/constraints) applied once at startup. Each is run as

View File

@@ -192,6 +192,19 @@ public class GraphRepository {
});
}
/**
* Deletes all nodes and relationships, leaving an empty database. Schema
* (indexes/constraints) is unaffected.
*/
public Uni<Void> clearAll() {
return Uni.createFrom().item(() -> {
try (Session session = driver.session()) {
session.executeWriteWithoutResult(tx -> tx.run(CypherQueries.CLEAR_ALL).consume());
}
return null;
}).replaceWithVoid();
}
public Uni<List<ProjectInfo>> listProjects() {
return Uni.createFrom().item(() -> {
try (Session session = driver.session()) {