From spring
Build Spring Shell command-line applications with validated commands, completion, availability rules, terminal prompts, and shell-focused tests. Use this skill when building Spring Shell command-line applications, interactive REPL workflows, validated commands, completion, command availability rules, terminal prompts, and shell-focused tests.
npx claudepluginhub ririnto/sinon --plugin springThis skill uses the workspace's default tool permissions.
Use this skill when building operational CLIs, administrative shells, interactive command workflows, or shell-focused tests with Spring Shell command registration, option parsing, completion, availability, and terminal-aware input or output.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
Use this skill when building operational CLIs, administrative shells, interactive command workflows, or shell-focused tests with Spring Shell command registration, option parsing, completion, availability, and terminal-aware input or output.
The latest released Spring Shell line is 4.0.1. In 4.x, @Command is the ordinary command model and the built-in exit command is exit, not quit, so keep older 3.x command examples out of the common path.
Use spring-shell for command registration, command grouping, option syntax, validation, completion, availability checks, shell-specific prompting, terminal-facing output, and shell-focused testing.
CommandLineRunner or ApplicationRunner for one-shot startup jobs that do not expose an interactive command surface.| Surface | Start here when | Open a reference when |
|---|---|---|
| Ordinary commands | one command with options, help text, and deterministic output is enough | stay in SKILL.md |
| Availability and completion | a command needs state checks or bounded values | stay in SKILL.md |
| Headless or scripted execution | the shell must run without the interactive REPL loop | stay in SKILL.md |
| Guided flows or selection widgets | plain options become error-prone or too hard to discover | open references/interactive-flows-and-terminal-ui.md |
| Prompt or output risk signaling | environment, login state, or operator risk must stay visible | open references/prompt-and-styling.md |
The ordinary Spring Shell job is:
CommandContext input and output APIs when a command needs interactive reads or terminal-aware writes.SKILL.md for the ordinary command path: annotation-based commands, command grouping, built-in help behavior, option defaults, validation, completion, availability, CommandContext reads and writes, stable output, exit-status decisions, headless execution, and shell-focused tests.Use the starter for normal Spring Boot integration. Add the test module whenever command behavior matters enough to lock with executable shell tests.
<dependencies>
<dependency>
<groupId>org.springframework.shell</groupId>
<artifactId>spring-shell-starter</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.shell</groupId>
<artifactId>spring-shell-test</artifactId>
<version>4.0.1</version>
<scope>test</scope>
</dependency>
</dependencies>
| Need | Starter or artifact |
|---|---|
Ordinary @Command registration and REPL runtime | spring-shell-starter |
| Shell-focused tests | spring-shell-test |
| Completion and availability rules | spring-shell-starter |
| Guided flows or TUI components | spring-shell-starter, then open references/interactive-flows-and-terminal-ui.md |
| Prompt customization and styled operator output | spring-shell-starter, then open references/prompt-and-styling.md |
./mvnw test -Dtest=CatalogCommandsTests
./gradlew test --tests CatalogCommandsTests
printf 'help
exit
' | java -jar build/libs/app.jar
java -jar build/libs/app.jar --spring.shell.interactive.enabled=false catalog item add --sku SKU-1
spring:
shell:
interactive:
enabled: true
script:
enabled: true
Keep interactive mode enabled for the ordinary 4.x REPL path. Disable it only for automated script execution or harnesses that intentionally bypass the REPL loop.
Spring Shell can expose built-in commands such as help, clear, exit, script, and version.
help prints.@Option defaults, arity, and required flags to make invalid input fail early.Availability checks as close as possible to the command boundary.CommandContext for interactive reads and terminal-aware writes instead of System.in or System.out.@SpringBootApplication
class CatalogShellApplication {
}
@Service
class CatalogService {
String add(String sku, int quantity) {
return "added sku=%s quantity=%d".formatted(sku, quantity);
}
}
@Command(group = "catalog")
class CatalogCommands {
private final CatalogService catalogService;
CatalogCommands(CatalogService catalogService) {
this.catalogService = catalogService;
}
@Command(command = "item add", description = "Add an item")
String add(@Option(longNames = "sku", required = true) String sku, @Option(longNames = "quantity", defaultValue = "1") int quantity) {
return catalogService.add(sku, quantity);
}
}
@Command(group = "catalog")
class CatalogCommands {
private final CatalogService catalog;
CatalogCommands(CatalogService catalog) {
this.catalog = catalog;
}
@Command(command = "item add", description = "Add an item", help = "Adds an item to the catalog with an explicit SKU and optional quantity.")
String add(@Option(longNames = "sku", required = true) String sku, @Option(longNames = "quantity", defaultValue = "1") int quantity) {
catalog.add(sku, quantity);
return "added sku=%s quantity=%d".formatted(sku, quantity);
}
}
@Command(command = "item note", description = "Attach a free-form note")
String note(CommandContext ctx, @Option(longNames = "sku", required = true) String sku) {
String note = ctx.inputReader().readInput("note: ");
ctx.outputWriter().println("saving note for " + sku);
ctx.outputWriter().flush();
return "saved";
}
Use readPassword("prompt: ") instead of readInput(...) when the operator enters a secret.
@Command(group = "cluster")
class ClusterCommands {
private final ClusterSession clusterSession;
ClusterCommands(ClusterSession clusterSession) {
this.clusterSession = clusterSession;
}
@Command(command = "cluster status", description = "Show current cluster status")
String status() {
return clusterSession.currentStatus();
}
public Availability statusAvailability() {
return clusterSession.isConnected()
? Availability.available()
: Availability.unavailable("run 'cluster connect' first");
}
}
@Bean
ValueProvider environmentValueProvider() {
return completionContext -> Arrays.asList("local", "staging", "prod").stream()
.filter(candidate -> candidate.startsWith(completionContext.currentWordUpToCursor()))
.map(CompletionProposal::new)
.toList();
}
@Command(command = "cluster connect", description = "Connect to a target environment")
String connect(@Option(longNames = "env", valueProvider = "environmentValueProvider") String environment) {
return "connected %s".formatted(environment);
}
@Bean
ExitStatusExceptionMapper exitStatusExceptionMapper() {
return exception -> exception instanceof IllegalArgumentException ? 2 : 1;
}
Use explicit exit-status mapping only when scripts or operators depend on a stable non-zero contract.
@ShellTest
@ContextConfiguration(classes = CatalogShellApplication.class)
class CatalogCommandsTests {
@Test
void addCommandReturnsDeterministicOutput(@Autowired ShellTestClient client) throws Exception {
ShellScreen shellScreen = client.sendCommand("catalog item add --sku SKU-1 --quantity 2");
ShellAssertions.assertThat(shellScreen).containsText("added sku=SKU-1 quantity=2");
}
}
Return:
added sku=SKU-1 quantity=2
Command 'cluster status' exists but is not currently available because run 'cluster connect' first
catalog item add --sku <string> [--quantity <int>]
exit code: 2
@ShellTest path when parsing, dispatch, and terminal output are the real contract under test.@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, useMainMethod = SpringBootTest.UseMainMethod.ALWAYS) path when full application wiring matters beyond @ShellTest.printf 'item add --sku SKU-1\nexit\n' | java -jar build/libs/app.jar
Expected output shape:
added sku=SKU-1 quantity=1