Guides
CLI Tool Licensing
Protect your command-line tool with license validation on every invocation.
Template used
This guide uses the
cli_tool policy template, which sets: 3 devices, offline 7 days, fingerprint required.1. Create Product and Policy
Create a product with a short prefix (e.g., "CLI") and apply the CLI tool template for sensible defaults.
setup.py
from licentric import Licentric
client = Licentric(api_key="lk_live_your_key_here")
product = client.products.create(
name="My CLI Tool",
code="CLI"
)
policy = client.policies.create(
name="CLI License",
product_id=product.id,
template="cli_tool" # 3 devices, offline 7 days
)2. Build an Activate Command
Provide an activate subcommand so users can register their license key. The key is saved to a local config file.
activate.py
def activate_command(license_key: str):
"""Handle the 'mytool activate <key>' command."""
result = client.validate(
key=license_key,
fingerprint=client.fingerprint()
)
if result.valid:
save_key_to_config(license_key)
print("License activated successfully!")
return
if result.code == "FINGERPRINT_NOT_FOUND":
# Activate this device
client.machines.activate(
license_id=result.license_id,
fingerprint=client.fingerprint()
)
save_key_to_config(license_key)
print("Device activated and license saved!")
return
print(f"Activation failed: {result.code}")3. Store the Key in Config
Store the license key in a user-level config file so it persists across sessions. TOML is a natural choice for CLI tools.
config.py
# ~/.mytool/config.toml
[license]
key = "CLI-ABCD-EFGH-IJKL-MNOP"
# Python: reading the config
import tomllib
from pathlib import Path
def load_key_from_config() -> str | None:
config_path = Path.home() / ".mytool" / "config.toml"
if not config_path.exists():
return None
with open(config_path, "rb") as f:
config = tomllib.load(f)
return config.get("license", {}).get("key")4. Validate on Every Command
CLI tools should validate the license before executing any command. Use caching to avoid adding network latency to every invocation.
Python
cli.py
import sys
from licentric import Licentric, ApiError
client = Licentric(api_key="lk_live_your_key_here")
def require_license():
"""Validate license before running any CLI command."""
key = load_key_from_config()
if not key:
print("No license key found. Run 'mytool activate' first.")
sys.exit(1)
try:
result = client.validate(
key=key,
fingerprint=client.fingerprint()
)
if not result.valid:
print(f"License invalid: {result.code}")
sys.exit(1)
except ApiError:
# Offline — check cached validation
cached = load_cached_validation()
if not cached or cached.expired:
print("Cannot validate license offline. Connect to the internet.")
sys.exit(1)TypeScript
cli.ts
import { Licentric } from "@licentric/sdk";
const client = new Licentric({ apiKey: "lk_live_your_key_here" });
async function requireLicense(): Promise<void> {
const key = loadKeyFromConfig();
if (!key) {
process.stderr.write("No license key found. Run 'mytool activate' first.\n");
process.exit(1);
}
const result = await client.validate({
key,
fingerprint: client.fingerprint(),
});
if (!result.valid) {
process.stderr.write(`License invalid: ${result.code}\n`);
process.exit(1);
}
}5. Cache Validation Results
Avoid hitting the API on every CLI invocation. Cache the validation result locally with a short TTL (5 minutes recommended) for a fast user experience.
cache.py
import json
import time
from pathlib import Path
CACHE_PATH = Path.home() / ".mytool" / ".license_cache.json"
CACHE_TTL = 300 # 5 minutes
def validate_with_cache(license_key: str) -> bool:
"""Cache validation results to avoid latency on every command."""
# Check cache first
if CACHE_PATH.exists():
cache = json.loads(CACHE_PATH.read_text())
if time.time() - cache["timestamp"] < CACHE_TTL:
return cache["valid"]
# Cache miss or expired — validate online
result = client.validate(
key=license_key,
fingerprint=client.fingerprint()
)
# Update cache
CACHE_PATH.write_text(json.dumps({
"valid": result.valid,
"code": result.code,
"timestamp": time.time()
}))
return result.validPerformance tip
A 5-minute cache TTL is a good balance between security and speed. The first command in each session validates online; subsequent commands within the TTL use the cache instantly.
Security
Store the config file in the user's home directory with restricted permissions (e.g.,
chmod 600). Never store the API key in the config file — only the license key.