Build complex CLIs with type safety and no dependencies
Application configuration and context threading for custom data and behavior control.
// Basic configuration
const app = buildApplication(command, {
name: "myapp",
versionInfo: { currentVersion: "1.0.0" }
})
// With custom settings
const app = buildApplication(command, {
name: "myapp",
versionInfo: { currentVersion: "1.0.0" },
scanner: { caseStyle: "allow-kebab-for-camel", allowArgumentEscapeSequence: true },
documentation: { useAliasInUsageLine: true, disableAnsiColor: false },
determineExitCode: (exc) => exc instanceof MyError ? exc.code : 1
})
// Custom context
interface MyContext extends CommandContext {
database: Database;
logger: Logger;
}
await run(app, inputs, {
process,
async forCommand(info) {
return { process, database: await connectDB(), logger: createLogger() };
}
})interface PartialApplicationConfiguration {
name: string // Required: application name
versionInfo?: VersionInfo
scanner?: Partial<ScannerConfiguration>
documentation?: Partial<DocumentationConfiguration>
completion?: Partial<CompletionConfiguration>
localization?: Partial<LocalizationConfiguration>
determineExitCode?: (exc: unknown) => number
}Controls command/argument scanning behavior.
scanner: {
caseStyle: "original" | "allow-kebab-for-camel", // default: "original"
allowArgumentEscapeSequence: boolean, // default: false (enables -- separator)
distanceOptions: { // "did you mean?" suggestions
threshold: number, // default: 7
weights: {
insertion: number, // default: 1
deletion: number, // default: 3
substitution: number, // default: 2
transposition: number // default: 0
}
}
}caseStyle:
"original": Exact match only (--myFlag)"allow-kebab-for-camel": Accept both --myFlag and --my-flagallowArgumentEscapeSequence: If true, -- marks end of flags (all subsequent inputs treated as arguments).
Controls help text formatting.
documentation: {
alwaysShowHelpAllFlag: boolean, // default: false
useAliasInUsageLine: boolean, // default: false
onlyRequiredInUsageLine: boolean, // default: false
caseStyle: "original" | "convert-camel-to-kebab", // default: derived from scanner
disableAnsiColor: boolean // default: false
}versionInfo: {
currentVersion: "1.0.0", // Static version
// OR
getCurrentVersion: async function() { return "1.0.0" }, // Dynamic version
// Optional:
getLatestVersion: async function(currentVersion) { /* check registry */ },
upgradeCommand: "npm install -g myapp@latest"
}determineExitCode: (exc) => {
if (exc instanceof BusinessError) return exc.exitCode;
if (exc instanceof Error && exc.message.includes("timeout")) return 124;
return 1;
}Base context interface with process streams.
interface CommandContext {
process: {
stdout: Writable;
stderr: Writable;
}
}Extend CommandContext for application-specific data.
interface MyContext extends CommandContext {
database: Database;
logger: Logger;
config: Config;
}
const command = buildCommand({
func: async function(this: MyContext, flags) {
await this.database.connect();
this.logger.info("Connected");
this.process.stdout.write("Done\n");
},
parameters: {},
docs: { brief: "Command" }
});Dynamic context creation per command.
await run(app, inputs, {
process,
locale: "en",
async forCommand(info) {
// info.prefix contains command route (e.g., ["myapp", "db", "migrate"])
return {
process,
database: await connectDB(),
logger: createLogger(info.prefix.join(" "))
};
}
});import { buildApplication, run } from "@stricli/core";
interface AppContext extends CommandContext {
database: Database;
logger: Logger;
}
class AppError extends Error {
constructor(message: string, public exitCode: number) {
super(message);
}
}
const app = buildApplication<AppContext>(command, {
name: "myapp",
versionInfo: {
currentVersion: "2.1.0",
async getLatestVersion() {
const res = await fetch("https://api.example.com/version");
return res.json().then(d => d.latest);
},
upgradeCommand: "npm install -g myapp@latest"
},
scanner: {
caseStyle: "allow-kebab-for-camel",
allowArgumentEscapeSequence: true
},
documentation: {
useAliasInUsageLine: true,
disableAnsiColor: false
},
determineExitCode: (exc) => {
if (exc instanceof AppError) return exc.exitCode;
return 1;
}
});
await run(app, process.argv.slice(2), {
process,
locale: process.env.LANG,
async forCommand(info) {
return {
process,
database: await connectDB(),
logger: createLogger(info.prefix.join(" "))
};
}
});STRICLI_SKIP_VERSION_CHECK=1: Skip automatic version checkingSTRICLI_NO_COLOR=1: Disable ANSI color outputInstall with Tessl CLI
npx tessl i tessl/npm-stricli--core