Config-driven CLI runner for template/bootstrap and sync workflows.
- Moved CLI implementation from legacy
code/layout to rootsrc/layout. - Switched build setup to
rollup.config.mjs+ TypeScript declarations output indist/. - Added executable CLI entrypoint (
dist/cli.js) with aliases:ill,aic,infinity-cli. - Kept plugin-based runtime and covered key paths with tests.
As a package dependency:
npm install @infinityloop.labs/ai-cliAlternative package managers:
yarn add @infinityloop.labs/ai-cli
pnpm add @infinityloop.labs/ai-cliGlobal install (optional):
npm install -g @infinityloop.labs/ai-cliShow help:
ill --helpInitialize config in current folder:
ill init --repo owner/template-repo --target-repo owner/product-repo --ref mainSync with template repository using configured command:
ill syncRun any configured command key:
ill <commandKey> --name MyFeatureRun command keys declared in infinityloop.config.*:
ill addWidget Header
ill addService Auth
ill addOpenApiConfig Billing
ill removeWidget Header
ill removeService Auth
ill removeOpenApiConfig BillingExtended form:
ill <commandKey> --name MyFeature --config ./infinityloop.config.js --cwd .Supported command aliases:
illaicinfinity-cli
npm run build- clean, bundle, generate declaration files, set execute bit on CLI output.npm run clean- removedist/.npm run test- run Node test suite (src/**/*.test.ts).npm run typecheck- TypeScript check without emit.npm run watch- TypeScript watch mode.npm run start- build and run CLI fromdist/cli.js.
CLI auto-detects one of:
infinityloop.config.jsinfinityloop.config.mjsinfinityloop.config.cjs
commands is a map where each key is a command and value is an array of steps.
module.exports = {
commands: {
createWidget: [
{
type: "add",
from: "_templates/react_template/_template/widget",
to: "generated/widgets/$name",
replace: [{ Sample: "$name" }, { sample: "$name" }],
},
],
removeWidget: [
{
type: "remove",
target: "generated/widgets/$name",
},
],
},
};add: copy file/folder fromfromtotowith optionalreplace.copy: copy file/folder fromfromtotowithout substitutions.download: clone template repository and copy intocwdwithout.gitand.github..gitignoreis preserved. Works in non-empty folders by default; setallowNonEmpty: falseto require empty target.merge-template: подтягивает снапшот шаблона, строит 3-way patch и накладывает его поверх текущей рабочей копии. По умолчанию CLI предложит выбрать удаляемые файлы в интерактивном режиме. Если удалений быть не должно вовсе, установитеallowDeletes: false. МассивprotectedPaths(пути относительно корня проекта) позволяет заблокировать удаление конкретных директорий/файлов даже при включённых удалениях.insert: insertlineafterplaceholderinfile.replace: replace the first occurrence ofsearchwithreplaceinfile.rename: replace tokens in file contents and file/directory names insidetargetwith case preservation (Sample/sample/SAMPLE).remove-line: remove a line fromfileby text match.remove: delete file/folder attarget.read: load variables from a key=value file (for example.cli) and inject them into the command context so subsequent steps can use${PROJECT_NAME}placeholders.
- Fields such as
file,placeholder,line,search, andreplaceaccept either a string with$variable/${variable}placeholders or a function(variables) => string. - Available variables:
name,namePascal,nameCamel,nameLower,nameSnake,nameKebab,nameScreamingSnake
- Any extra CLI flag becomes a variable:
--store-name=SideMenuexposes${storeName},--dry-runsets${dryRun}to"true", and disabling flags like--no-store/--nostoreset${store}to"false"while${noStore}becomes"true". Flag names are normalized to camelCase by stripping dashes/underscores, but you can still reference hyphenated names inwhenexpressions (e.g.when: "!no-store"). - The
readstep is typically used to load a.clifile with entries likePROJECT_NAME=my-app. EachKEY=VALUEpair becomes available via${KEY}or${key}in later steps. - For
${variable}syntax, the placeholder casing controls the transform:${name}lowers the first letter,${Name}capitalizes it,${NAME}uppercases the whole string. Plain$variablekeeps the stored value. - Example:
{
type: "insert",
file: "app/features/services/$name/hooks.ts",
placeholder: "// Services: Start",
line: " $nameLower: createAction(${name}Actions),",
}- Every step may declare
when, either as a string ("store","!store") or an array of such strings. All conditions must be truthy for the step to run. - Truthiness is based on the variable value: missing/
"false"/"0"/empty strings are treated asfalse, everything else istrue. Prefix!to invert the result. - Example: skip store-related insertions when
--no-storeis provided.
{
type: "insert",
when: "!no-store", // executes only when --no-store/--nostore is not provided
file: "app/store/index.ts",
placeholder: "// Services: Start",
line: " ${name}: ${Name}Reducer,",
}
// merge-template example that blocks deletions but still fetches updates
{
type: "merge-template",
repo: TEMPLATE_REPO,
ref: TEMPLATE_REF,
allowDeletes: false,
protectedPaths: [".cli", "app/business"],
}