Build complex CLIs with type safety and no dependencies
Comprehensive error types for argument parsing and validation with "did you mean?" suggestions.
// Safe error handling in commands (return Error instead of throwing)
func: async function(flags) {
try {
await operation();
} catch (err) {
return new Error(`Operation failed: ${err.message}`);
}
}
// Catch scanner errors
try {
await run(app, inputs, { process });
} catch (err) {
if (err instanceof ArgumentScannerError) {
// Handle parsing errors
console.error(err.message);
}
}All argument scanning errors extend ArgumentScannerError.
abstract class ArgumentScannerError extends Error| Error | When | Properties |
|---|---|---|
FlagNotFoundError | Unknown flag name | input, corrections, aliasName? |
AliasNotFoundError | Unknown alias letter | input |
ArgumentParseError | Parser threw exception | externalFlagNameOrPlaceholder, input, exception |
EnumValidationError | Invalid enum value | externalFlagName, input, values, corrections |
UnsatisfiedFlagError | Missing required flag value | externalFlagName, nextFlagName? |
UnsatisfiedPositionalError | Missing required argument | placeholder, limit? |
UnexpectedPositionalError | Too many arguments | expectedCount, input |
UnexpectedFlagError | Flag specified multiple times (non-variadic) | externalFlagName, previousInput, input |
InvalidNegatedFlagSyntaxError | Value provided for negated flag | externalFlagName, valueText |
// FlagNotFoundError
// myapp --verbos
// Error: No flag registered for --verbos, did you mean --verbose?
// ArgumentParseError
// myapp --port abc
// Error: Failed to parse "abc" for port: Cannot convert abc to a number
// EnumValidationError
// myapp --env prodution
// Error: Expected "prodution" to be one of (dev|staging|prod), did you mean "prod"?
// UnsatisfiedFlagError
// myapp --output
// Error: Expected input for flag --output
// UnsatisfiedPositionalError
// myapp (expects 1 arg)
// Error: Expected argument for arg1
// UnexpectedPositionalError
// myapp file1 file2 file3 (expects max 2)
// Error: Too many arguments, expected 2 but encountered "file3"import { formatMessageForArgumentScannerError } from "@stricli/core";
function handleError(error: ArgumentScannerError): string {
return formatMessageForArgumentScannerError(error, {
FlagNotFoundError: (err) => {
if (err.corrections.length > 0) {
return `Unknown flag: ${err.input}. Try: ${err.corrections.join(", ")}`;
}
return `Unknown flag: ${err.input}`;
},
ArgumentParseError: (err) => {
return `Invalid value "${err.input}" for ${err.externalFlagNameOrPlaceholder}`;
}
});
}
try {
await run(app, inputs, { process });
} catch (err) {
if (err instanceof ArgumentScannerError) {
const message = handleError(err);
console.error(message);
}
}Commands can return Error instead of throwing for graceful error handling.
const command = buildCommand({
func: async function(flags, filePath) {
try {
const content = await readFile(filePath);
this.process.stdout.write(content);
} catch (err) {
// Return Error - Stricli handles it gracefully
return new Error(`Failed to read file: ${err.message}`);
}
},
parameters: {
positional: {
kind: "tuple",
parameters: [{ brief: "File path", parse: String }]
}
},
docs: { brief: "Read file" }
});import {
buildApplication,
buildCommand,
run,
ArgumentScannerError,
FlagNotFoundError,
ArgumentParseError,
formatMessageForArgumentScannerError
} from "@stricli/core";
const app = buildApplication(
buildCommand({
func: async function(flags) {
try {
// Operation
this.process.stdout.write("Success\n");
} catch (err) {
return new Error(`Operation failed: ${err.message}`);
}
},
parameters: {
flags: {
port: {
kind: "parsed",
parse: (input) => {
const port = parseInt(input, 10);
if (isNaN(port) || port < 1 || port > 65535) {
throw new Error("must be 1-65535");
}
return port;
},
brief: "Port"
}
}
},
docs: { brief: "Start server" }
}),
{ name: "server" }
);
try {
await run(app, process.argv.slice(2), { process });
} catch (err) {
if (err instanceof ArgumentScannerError) {
const message = formatMessageForArgumentScannerError(err, {
FlagNotFoundError: (e) =>
`ERROR: Flag "${e.input}" doesn't exist${
e.corrections.length > 0 ? `. Did you mean: ${e.corrections.join(", ")}?` : ""
}`,
ArgumentParseError: (e) =>
`ERROR: Invalid value "${e.input}" for --${e.externalFlagNameOrPlaceholder}. ${
e.exception instanceof Error ? e.exception.message : ""
}`
});
process.stderr.write(message + "\n");
process.exitCode = 1;
} else {
throw err;
}
}Install with Tessl CLI
npx tessl i tessl/npm-stricli--core