Skip to content

Commit d47ec69

Browse files
✨ add support for v2 CLI (#331)
1 parent 2a8f147 commit d47ec69

15 files changed

Lines changed: 624 additions & 2 deletions

.github/workflows/_test-cli.yml

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

.github/workflows/cron.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@ on:
44
schedule:
55
- cron: '33 0 * * *'
66

7+
permissions:
8+
contents: read
9+
710
jobs:
811
codeql:
912
uses: mindee/mindee-api-java/.github/workflows/_codeql.yml@main
1013
test_code_samples:
1114
uses: mindee/mindee-api-java/.github/workflows/_test-code-samples.yml@main
1215
secrets: inherit
16+
test_cli:
17+
uses: mindee/mindee-api-java/.github/workflows/_test-cli.yml@main
18+
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)