Skip to content

Commit bffee1f

Browse files
✨ add support for v2 CLI
1 parent 2a8f147 commit bffee1f

15 files changed

Lines changed: 618 additions & 2 deletions

.github/workflows/_test-cli.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Test Command Line Interface
2+
3+
on:
4+
workflow_call:
5+
workflow_dispatch:
6+
7+
env:
8+
MINDEE_API_KEY: ${{ secrets.MINDEE_API_KEY_SE_TESTS }}
9+
MINDEE_V2_API_KEY: ${{ secrets.MINDEE_V2_SE_TESTS_API_KEY }}
10+
MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID }}
11+
MINDEE_V2_SE_TESTS_CLASSIFICATION_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_CLASSIFICATION_MODEL_ID }}
12+
MINDEE_V2_SE_TESTS_CROP_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_CROP_MODEL_ID }}
13+
MINDEE_V2_SE_TESTS_SPLIT_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_SPLIT_MODEL_ID }}
14+
MINDEE_V2_SE_TESTS_OCR_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_OCR_MODEL_ID }}
15+
16+
jobs:
17+
test:
18+
name: Run CLI tests
19+
timeout-minutes: 30
20+
runs-on: ubuntu-latest
21+
22+
strategy:
23+
matrix:
24+
java-version:
25+
- "25"
26+
- "11"
27+
distribution:
28+
- "temurin"
29+
30+
steps:
31+
- uses: actions/checkout@v5
32+
with:
33+
submodules: recursive
34+
35+
- name: Set up JDK ${{ matrix.java-version }}
36+
uses: actions/setup-java@v5
37+
with:
38+
java-version: ${{ matrix.java-version }}
39+
distribution: ${{ matrix.distribution }}
40+
cache: "maven"
41+
42+
- name: Build JAR
43+
run: mvn package -DskipTests --no-transfer-progress
44+
45+
- name: Test V1 CLI
46+
run: ./tests/test_v1_cli.sh ./src/test/resources/file_types/pdf/blank_1.pdf
47+
48+
- name: Test V2 CLI
49+
run: ./tests/test_v2_cli.sh ./src/test/resources/file_types/pdf/blank_1.pdf

.github/workflows/cron.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ jobs:
1010
test_code_samples:
1111
uses: mindee/mindee-api-java/.github/workflows/_test-code-samples.yml@main
1212
secrets: inherit
13+
test_cli:
14+
uses: mindee/mindee-api-java/.github/workflows/_test-cli.yml@main
15+
secrets: inherit

.github/workflows/pull-request.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,7 @@ jobs:
2929
uses: ./.github/workflows/_test-code-samples.yml
3030
needs: build
3131
secrets: inherit
32+
test_cli:
33+
uses: ./.github/workflows/_test-cli.yml
34+
needs: build
35+
secrets: inherit

cli.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ if ! ls ./target/libs/picocli-* 2>&1 >/dev/null; then
55
mvn dependency:copy-dependencies -DoutputDirectory=target/libs -Dhttps.protocols=TLSv1.2
66
fi
77

8-
VERSION=$(grep -m1 -o -P '(?<=\<version\>)[0-9.]*(?!/<\/version>)' pom.xml)
8+
VERSION=$(grep -m1 -o -P '(?<=<revision>)[^<]+' pom.xml)
99

1010
java -cp "target/mindee-api-java-${VERSION}.jar:target/libs/*" com.mindee.CommandLineInterface "$@"
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.mindee;
2+
3+
import com.mindee.v1.cli.CommandLineInterfaceProducts;
4+
import com.mindee.v1.cli.ProductProcessor;
5+
import com.mindee.v2.cli.ClassificationCommand;
6+
import com.mindee.v2.cli.CropCommand;
7+
import com.mindee.v2.cli.ExtractionCommand;
8+
import com.mindee.v2.cli.OcrCommand;
9+
import com.mindee.v2.cli.SearchModelsCommand;
10+
import com.mindee.v2.cli.SplitCommand;
11+
import java.lang.reflect.Method;
12+
import picocli.CommandLine;
13+
import picocli.CommandLine.Command;
14+
import picocli.CommandLine.Model.CommandSpec;
15+
import picocli.CommandLine.Spec;
16+
17+
/**
18+
* Top-level Mindee CLI entry point.
19+
* <p>
20+
* V2 commands (search-models, classification, crop, extraction, ocr, split) are available at the
21+
* root level. V1 product commands are available under the {@code v1} subcommand.
22+
*/
23+
@Command(
24+
name = "mindee",
25+
description = "Mindee API client",
26+
mixinStandardHelpOptions = true,
27+
subcommands = { CommandLine.HelpCommand.class }
28+
)
29+
public class CommandLineInterface implements Runnable {
30+
31+
@Spec
32+
private CommandSpec spec;
33+
34+
/**
35+
* Main entry point for the Mindee CLI.
36+
*
37+
* @param args command-line arguments
38+
*/
39+
public static void main(String[] args) {
40+
CommandLineInterface root = new CommandLineInterface();
41+
CommandLine rootCmd = new CommandLine(root);
42+
43+
// V2 commands at root
44+
rootCmd.addSubcommand("search-models", new CommandLine(new SearchModelsCommand()));
45+
rootCmd.addSubcommand("classification", new CommandLine(new ClassificationCommand()));
46+
rootCmd.addSubcommand("crop", new CommandLine(new CropCommand()));
47+
rootCmd.addSubcommand("extraction", new CommandLine(new ExtractionCommand()));
48+
rootCmd.addSubcommand("ocr", new CommandLine(new OcrCommand()));
49+
rootCmd.addSubcommand("split", new CommandLine(new SplitCommand()));
50+
51+
// V1 commands under the "v1" subcommand
52+
var v1Cli = new com.mindee.v1.CommandLineInterface();
53+
var v1Cmd = new CommandLine(v1Cli);
54+
v1Cmd.getCommandSpec().name("v1");
55+
v1Cmd.getCommandSpec().usageMessage().description("Mindee V1 product commands.");
56+
var products = new CommandLineInterfaceProducts((ProductProcessor) v1Cli);
57+
for (Method method : CommandLineInterfaceProducts.class.getDeclaredMethods()) {
58+
if (method.isAnnotationPresent(CommandLine.Command.class)) {
59+
CommandLine.Command annotation = method.getAnnotation(CommandLine.Command.class);
60+
String subcommandName = annotation.name();
61+
var subCmd = new CommandLine(
62+
new com.mindee.v1.CommandLineInterface.ProductCommandHandler(products, method)
63+
);
64+
subCmd.getCommandSpec().usageMessage().description(annotation.description());
65+
v1Cmd.addSubcommand(subcommandName, subCmd);
66+
}
67+
}
68+
rootCmd.addSubcommand("v1", v1Cmd);
69+
70+
System.exit(rootCmd.execute(args));
71+
}
72+
73+
@Override
74+
public void run() {
75+
spec.commandLine().usage(System.out);
76+
}
77+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package com.mindee.v2.cli;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.fasterxml.jackson.databind.SerializationFeature;
5+
import com.mindee.input.LocalInputSource;
6+
import com.mindee.v2.MindeeClient;
7+
import com.mindee.v2.parsing.CommonResponse;
8+
import java.io.File;
9+
import java.util.concurrent.Callable;
10+
import picocli.CommandLine.Option;
11+
import picocli.CommandLine.Parameters;
12+
13+
/**
14+
* Abstract base class for V2 inference CLI commands.
15+
* Handles common options (path, model-id, api-key, alias, output) and output formatting.
16+
*/
17+
public abstract class BaseInferenceCommand implements Callable<Integer> {
18+
19+
@Parameters(index = "0", paramLabel = "<path>", description = "The path of the file to parse")
20+
protected File file;
21+
22+
@Option(names = { "-m", "--model-id" }, description = "ID of the model to use", required = true)
23+
protected String modelId;
24+
25+
@Option(names = { "-k", "--api-key" }, description = "Mindee V2 API key.")
26+
protected String apiKey;
27+
28+
@Option(names = { "-a", "--alias" }, description = "Alias for the file")
29+
protected String alias;
30+
31+
/** Output format for the command. */
32+
public enum OutputType {
33+
Summary,
34+
Full,
35+
Raw
36+
}
37+
38+
@Option(
39+
names = { "-o", "--output" },
40+
description = "Specify how to output the data.\n"
41+
+ "- summary: a basic summary (default)\n"
42+
+ "- full: detail extraction results, including options\n"
43+
+ "- raw: full JSON object",
44+
defaultValue = "Summary"
45+
)
46+
protected OutputType output;
47+
48+
/**
49+
* Executes the inference request and returns the product response.
50+
*
51+
* @param client the V2 Mindee client
52+
* @param inputSource the prepared local input source
53+
* @return the product response
54+
* @throws Exception on IO or API error
55+
*/
56+
protected abstract CommonResponse executeRequest(
57+
MindeeClient client,
58+
LocalInputSource inputSource
59+
) throws Exception;
60+
61+
/**
62+
* Returns the summary (result-only) string for the given response.
63+
* Override in each product command.
64+
*
65+
* @param response the product response
66+
* @return the summary string
67+
*/
68+
protected abstract String getSummary(CommonResponse response);
69+
70+
/**
71+
* Returns the full (inference + options + result) string for the given response.
72+
* Defaults to the same as {@link #getSummary}; override for richer output.
73+
*
74+
* @param response the product response
75+
* @return the full output string
76+
*/
77+
protected String getFullOutput(CommonResponse response) {
78+
return getSummary(response);
79+
}
80+
81+
@Override
82+
public Integer call() throws Exception {
83+
var client = new MindeeClient(apiKey != null ? apiKey : "");
84+
var inputSource = new LocalInputSource(file);
85+
var response = executeRequest(client, inputSource);
86+
printOutput(response);
87+
return 0;
88+
}
89+
90+
private void printOutput(CommonResponse response) throws Exception {
91+
switch (output) {
92+
case Full:
93+
System.out.println(getFullOutput(response));
94+
break;
95+
case Raw:
96+
var mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
97+
var jsonNode = mapper.readTree(response.getRawResponse());
98+
System.out.println(mapper.writeValueAsString(jsonNode));
99+
break;
100+
default:
101+
System.out.println(getSummary(response));
102+
break;
103+
}
104+
}
105+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.mindee.v2.cli;
2+
3+
import com.mindee.input.LocalInputSource;
4+
import com.mindee.v2.MindeeClient;
5+
import com.mindee.v2.parsing.CommonResponse;
6+
import com.mindee.v2.product.classification.ClassificationResponse;
7+
import com.mindee.v2.product.classification.params.ClassificationParameters;
8+
import picocli.CommandLine.Command;
9+
10+
/**
11+
* CLI command for the V2 classification utility.
12+
*/
13+
@Command(
14+
name = "classification",
15+
description = "Classification utility.",
16+
mixinStandardHelpOptions = true
17+
)
18+
public class ClassificationCommand extends BaseInferenceCommand {
19+
20+
@Override
21+
protected CommonResponse executeRequest(
22+
MindeeClient client,
23+
LocalInputSource inputSource
24+
) throws Exception {
25+
return client
26+
.enqueueAndGetResult(
27+
ClassificationResponse.class,
28+
inputSource,
29+
ClassificationParameters.builder(modelId).alias(alias).build()
30+
);
31+
}
32+
33+
@Override
34+
protected String getSummary(CommonResponse response) {
35+
return ((ClassificationResponse) response).getInference().getResult().toString();
36+
}
37+
38+
@Override
39+
protected String getFullOutput(CommonResponse response) {
40+
return ((ClassificationResponse) response).getInference().toString();
41+
}
42+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.mindee.v2.cli;
2+
3+
import com.mindee.input.LocalInputSource;
4+
import com.mindee.v2.MindeeClient;
5+
import com.mindee.v2.parsing.CommonResponse;
6+
import com.mindee.v2.product.crop.CropResponse;
7+
import com.mindee.v2.product.crop.params.CropParameters;
8+
import picocli.CommandLine.Command;
9+
10+
/**
11+
* CLI command for the V2 crop utility.
12+
*/
13+
@Command(name = "crop", description = "Crop utility.", mixinStandardHelpOptions = true)
14+
public class CropCommand extends BaseInferenceCommand {
15+
16+
@Override
17+
protected CommonResponse executeRequest(
18+
MindeeClient client,
19+
LocalInputSource inputSource
20+
) throws Exception {
21+
return client
22+
.enqueueAndGetResult(
23+
CropResponse.class,
24+
inputSource,
25+
CropParameters.builder(modelId).alias(alias).build()
26+
);
27+
}
28+
29+
@Override
30+
protected String getSummary(CommonResponse response) {
31+
return ((CropResponse) response).getInference().getResult().toString();
32+
}
33+
34+
@Override
35+
protected String getFullOutput(CommonResponse response) {
36+
return ((CropResponse) response).getInference().toString();
37+
}
38+
}

0 commit comments

Comments
 (0)