Add interactive shell mode to ac-cli
Running the jar with no arguments now starts a psql-like REPL (JLine, with persistent history) that dispatches commands to the existing picocli subcommands, plus a new `connect` command to set the session server URL. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
22
README.md
22
README.md
@@ -185,6 +185,28 @@ java -jar ac-cli/target/ac-cli-1.0.0-SNAPSHOT.jar --help
|
||||
|
||||
By default the CLI talks to `http://localhost:8080`. Override with `-s/--server <url>` or the `AC_SERVER_URL` environment variable.
|
||||
|
||||
### Interactive shell
|
||||
|
||||
Running the jar with no arguments starts a `psql`-like interactive shell with line editing and history (`~/.agenticcode_history`):
|
||||
|
||||
```bash
|
||||
java -jar ac-cli/target/ac-cli-1.0.0-SNAPSHOT.jar
|
||||
```
|
||||
|
||||
```
|
||||
AgenticCode interactive shell. Type 'help' for commands, 'exit' to quit.
|
||||
agenticcode> connect http://my-server:8080
|
||||
Connected to http://my-server:8080
|
||||
agenticcode> callers MY-MODULE
|
||||
[ ... ]
|
||||
agenticcode> ingest ./src/main/natural
|
||||
OK ./src/main/natural/FOO.nat
|
||||
...
|
||||
agenticcode> exit
|
||||
```
|
||||
|
||||
`connect <url>` sets the server for the rest of the session (overridden by `-s` on an individual command). `help`/`?` shows available commands, `exit`/`quit` ends the session.
|
||||
|
||||
### Ingest files or folders
|
||||
|
||||
Recursively walks a file or directory; `.java` files are sent to `/api/ingest/java` and `.nat`/`.nsn` files to `/api/ingest/natural`. Files with other extensions are skipped.
|
||||
|
||||
6
ac-cli/ac-cli.iml
Normal file
6
ac-cli/ac-cli.iml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module version="4">
|
||||
<component name="CheckStyle-IDEA-Module" serialisationVersion="2">
|
||||
<option name="activeLocationsIds" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -29,6 +29,10 @@
|
||||
<groupId>org.jspecify</groupId>
|
||||
<artifactId>jspecify</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jline</groupId>
|
||||
<artifactId>jline</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
@@ -58,6 +62,7 @@
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>com.agenticcode.cli.AgenticCodeCli</mainClass>
|
||||
</transformer>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
@@ -11,18 +11,24 @@ import java.util.concurrent.Callable;
|
||||
*/
|
||||
abstract class AbstractApiCommand implements Callable<Integer> {
|
||||
|
||||
static final String SERVER_URL_PROPERTY = "ac.server.url";
|
||||
|
||||
private static final String DEFAULT_SERVER_URL = "http://localhost:8080";
|
||||
|
||||
@Option(names = {"-s", "--server"},
|
||||
description = "Base URL of the AgenticCode server (default: ${DEFAULT-VALUE}, or $AC_SERVER_URL)",
|
||||
description = "Base URL of the AgenticCode server (default: ${DEFAULT-VALUE}, "
|
||||
+ "or the most recent 'connect', or $AC_SERVER_URL)",
|
||||
defaultValue = DEFAULT_SERVER_URL)
|
||||
String server = DEFAULT_SERVER_URL;
|
||||
|
||||
protected ApiClient apiClient() {
|
||||
String baseUrl = server;
|
||||
if (DEFAULT_SERVER_URL.equals(server)) {
|
||||
String connected = System.getProperty(SERVER_URL_PROPERTY);
|
||||
String env = System.getenv("AC_SERVER_URL");
|
||||
if (env != null && !env.isBlank()) {
|
||||
if (connected != null && !connected.isBlank()) {
|
||||
baseUrl = connected;
|
||||
} else if (env != null && !env.isBlank()) {
|
||||
baseUrl = env;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,15 @@ package com.agenticcode.cli;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Model.CommandSpec;
|
||||
import picocli.CommandLine.Spec;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* Command-line client for ingesting source code and querying the AgenticCode AST graph API.
|
||||
*
|
||||
* <p>Run with a subcommand for one-shot use, or with no arguments to enter an interactive shell.
|
||||
*/
|
||||
@Command(
|
||||
name = "agenticcode",
|
||||
@@ -12,6 +18,7 @@ import picocli.CommandLine.Command;
|
||||
version = "AgenticCode CLI 1.0.0",
|
||||
description = "Ingest source code and query the AgenticCode AST graph API.",
|
||||
subcommands = {
|
||||
ConnectCommand.class,
|
||||
IngestCommand.class,
|
||||
CallersCommand.class,
|
||||
CalleesCommand.class,
|
||||
@@ -19,7 +26,11 @@ import picocli.CommandLine.Command;
|
||||
SearchIdentifierCommand.class
|
||||
}
|
||||
)
|
||||
public final class AgenticCodeCli implements Runnable {
|
||||
public final class AgenticCodeCli implements Callable<Integer> {
|
||||
|
||||
@SuppressWarnings("NullAway.Init")
|
||||
@Spec
|
||||
CommandSpec spec;
|
||||
|
||||
public static void main(String[] args) {
|
||||
int exitCode = new CommandLine(new AgenticCodeCli()).execute(args);
|
||||
@@ -27,7 +38,7 @@ public final class AgenticCodeCli implements Runnable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
CommandLine.usage(this, System.out);
|
||||
public Integer call() throws Exception {
|
||||
return new InteractiveShell(spec.commandLine()).run();
|
||||
}
|
||||
}
|
||||
|
||||
24
ac-cli/src/main/java/com/agenticcode/cli/ConnectCommand.java
Normal file
24
ac-cli/src/main/java/com/agenticcode/cli/ConnectCommand.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package com.agenticcode.cli;
|
||||
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Parameters;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* Sets the server URL used by subsequent commands in this session.
|
||||
*/
|
||||
@Command(name = "connect", mixinStandardHelpOptions = true, description = "Set the server URL for this session")
|
||||
final class ConnectCommand implements Callable<Integer> {
|
||||
|
||||
@SuppressWarnings("NullAway.Init")
|
||||
@Parameters(index = "0", description = "Base URL of the AgenticCode server")
|
||||
String url;
|
||||
|
||||
@Override
|
||||
public Integer call() {
|
||||
System.setProperty(AbstractApiCommand.SERVER_URL_PROPERTY, url);
|
||||
System.out.println("Connected to " + url);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.agenticcode.cli;
|
||||
|
||||
import org.jline.reader.EndOfFileException;
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.reader.LineReaderBuilder;
|
||||
import org.jline.reader.ParsedLine;
|
||||
import org.jline.reader.UserInterruptException;
|
||||
import org.jline.reader.impl.DefaultParser;
|
||||
import org.jline.reader.impl.history.DefaultHistory;
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.terminal.TerminalBuilder;
|
||||
import picocli.CommandLine;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Interactive read-eval-print loop dispatching command lines to the picocli command tree.
|
||||
*/
|
||||
final class InteractiveShell {
|
||||
|
||||
private static final Path HISTORY_FILE = Path.of(System.getProperty("user.home"), ".agenticcode_history");
|
||||
|
||||
private final CommandLine commandLine;
|
||||
|
||||
InteractiveShell(CommandLine commandLine) {
|
||||
this.commandLine = commandLine;
|
||||
}
|
||||
|
||||
int run() throws IOException {
|
||||
try (Terminal terminal = TerminalBuilder.builder().build()) {
|
||||
LineReader reader = LineReaderBuilder.builder()
|
||||
.terminal(terminal)
|
||||
.parser(new DefaultParser())
|
||||
.appName("agenticcode")
|
||||
.variable(LineReader.HISTORY_FILE, HISTORY_FILE)
|
||||
.build();
|
||||
new DefaultHistory(reader);
|
||||
|
||||
terminal.writer().println("AgenticCode interactive shell. Type 'help' for commands, 'exit' to quit.");
|
||||
|
||||
while (true) {
|
||||
String line;
|
||||
try {
|
||||
line = reader.readLine("agenticcode> ").trim();
|
||||
} catch (UserInterruptException e) {
|
||||
continue;
|
||||
} catch (EndOfFileException e) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (line.equals("exit") || line.equals("quit")) {
|
||||
break;
|
||||
}
|
||||
if (line.equals("help") || line.equals("?")) {
|
||||
commandLine.usage(terminal.writer());
|
||||
continue;
|
||||
}
|
||||
|
||||
ParsedLine parsed = reader.getParser().parse(line, line.length());
|
||||
commandLine.execute(parsed.words().toArray(new String[0]));
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
7
pom.xml
7
pom.xml
@@ -38,6 +38,7 @@
|
||||
<nullaway.version>0.13.1</nullaway.version>
|
||||
<jspecify.version>1.0.0</jspecify.version>
|
||||
<picocli.version>4.7.6</picocli.version>
|
||||
<jline.version>3.26.3</jline.version>
|
||||
<maven-shade-plugin.version>3.6.0</maven-shade-plugin.version>
|
||||
<ac-mvn-plugins.version>${project.version}</ac-mvn-plugins.version>
|
||||
</properties>
|
||||
@@ -109,6 +110,12 @@
|
||||
<artifactId>picocli</artifactId>
|
||||
<version>${picocli.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jline</groupId>
|
||||
<artifactId>jline</artifactId>
|
||||
<version>${jline.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user