@hotreloads/crux-cli
v1.0.0
Published
CLI tool for scaffolding and managing the @hotreloads/crux actor system
Downloads
63
Maintainers
Readme
@hotreloads/crux-cli
CLI tool for scaffolding and managing the @hotreloads/crux actor system in React, Next.js, and SvelteKit projects.
Crux separates business logic from UI by putting it inside actors — isolated units that communicate through typed ask/reply and pub/sub. This CLI sets up the actor system in your project and generates typed actor boilerplate so you can focus on logic, not wiring.
Quick Start
# Initialize crux in your project
npx @hotreloads/crux-cli init
# Create your first actor
npx @hotreloads/crux-cli add actor authThat's it. The CLI detects your framework, installs @hotreloads/crux, generates the actor system setup files, and wires everything together.
Requirements
- Node.js >= 18
- TypeScript 5.4+
- One of: React + Vite, Next.js (App Router), or SvelteKit
Commands
crux-cli init
Initializes the crux actor system in your project.
npx @hotreloads/crux-cli initWhat it does:
- Detects your framework from
package.json— React + Vite, Next.js, or SvelteKit. If ambiguous, prompts you to choose. - Asks where actor files should live (default:
src/actorsorsrc/lib/actorsfor Svelte). - Asks whether to enable devtools in development mode.
- Installs
@hotreloads/cruxusing your detected package manager (npm/yarn/pnpm/bun). - Generates setup files based on your framework.
- Writes
crux.config.jsonto your project root for subsequent commands.
Generated files for React + Vite:
src/actors/
actor-address.ts # Centralized actor address constants
register-actors.ts # ActorSystem creation + actor registration
context.tsx # ActorProvider + useActorRef hook
index.ts # Barrel exportGenerated files for Next.js App Router:
Same structure as React + Vite, with:
context.tsxincludes the"use client"directiveregister-actors.tsguards devtools withtypeof window !== "undefined"
Generated files for SvelteKit:
src/lib/actors/
actor-address.ts # Centralized actor address constants
register-actors.ts # ActorSystem creation + actor registration
store.ts # actorSubscribe / actorAsk wrappers
index.ts # Barrel exportAfter init — wrap your app:
React + Vite (main.tsx):
import { ActorProvider } from "./actors";
ReactDOM.createRoot(document.getElementById("root")!).render(
<ActorProvider>
<App />
</ActorProvider>
);Next.js (app/layout.tsx):
import { ActorProvider } from "@/actors";
export default function RootLayout({ children }) {
return (
<html>
<body>
<ActorProvider>{children}</ActorProvider>
</body>
</html>
);
}crux-cli add actor <name>
Creates a new actor with typed boilerplate and automatically wires it into your project.
npx @hotreloads/crux-cli add actor paymentsWhat it generates:
src/actors/payments/
payments-actor.ts # Actor class with example ask
payments-actor-types.ts # Type-safe AskMappayments-actor-types.ts:
import type { ActorAskMap } from "@hotreloads/crux";
export type PaymentsActorAsks = ActorAskMap & {
// Example: define your ask types here
getStatus: {
payload: { id: string };
result: { status: string };
};
};payments-actor.ts:
import { Actor } from "@hotreloads/crux";
import type { ActorResult } from "@hotreloads/crux";
import type { PaymentsActorAsks } from "./payments-actor-types";
export class PaymentsActor extends Actor {
static ASKS = {
GET_STATUS: "getStatus",
} as const;
static TOPICS = {
STATUS: "payments_status",
} as const;
async ask(asktype: string, data: unknown): Promise<ActorResult> {
switch (asktype) {
case PaymentsActor.ASKS.GET_STATUS: {
const payload = data as PaymentsActorAsks["getStatus"]["payload"];
// TODO: implement your logic here
this.publish(PaymentsActor.TOPICS.STATUS, { status: "active" }, true);
return [{ status: "active" }, null];
}
default:
return [null, this.errunsupportedask(asktype, data)];
}
}
}Auto-wiring (via ts-morph AST manipulation):
The CLI automatically modifies three existing files:
actor-address.ts— addsPAYMENTS_ACTOR: "payments_actor"to the address objectregister-actors.ts— adds the import andsystem.registerChild(...)callindex.ts— adds the barrel re-export
If the CLI can't find the expected code structure (e.g. you've heavily modified the files), it prints the exact lines to add manually instead of corrupting your code.
Using the actor in a component:
import { useActorRef } from "./actors";
import { ACTOR_ADDRESS } from "./actors";
function PaymentStatus({ id }: { id: string }) {
const ref = useActorRef(ACTOR_ADDRESS.PAYMENTS_ACTOR);
const handleCheck = async () => {
const [result, err] = await ref.current!.ask("getStatus", { id });
if (err) {
console.error(err.errmsg);
return;
}
console.log(result.status);
};
return <button onClick={handleCheck}>Check Status</button>;
}Name conventions:
| Input | Class | Address Constant | Address Value | Directory | Files |
|---|---|---|---|---|---|
| payments | PaymentsActor | PAYMENTS_ACTOR | payments_actor | payments/ | payments-actor.ts |
| bank-coupon | BankCouponActor | BANK_COUPON_ACTOR | bank_coupon_actor | bank-coupon/ | bank-coupon-actor.ts |
| ratingAgency | RatingAgencyActor | RATING_AGENCY_ACTOR | rating_agency_actor | rating-agency/ | rating-agency-actor.ts |
crux-cli add middleware
Scaffolds a middleware for the actor system.
npx @hotreloads/crux-cli add middlewarePrompts you to choose a middleware type:
| Type | Description |
|---|---|
| Logging | Logs all asks with timing (performance.now()) |
| Auth Guard | Blocks asks when not authenticated, with a configurable PUBLIC_ASKS set |
| Retry | Retries NETWORK_ERROR failures with exponential backoff (3 retries, 500ms base) |
| Custom | Blank template with before/after hooks |
Generates the file in src/actors/middleware/ and prints wiring instructions:
import { loggingMiddleware } from "./middleware/logging-middleware";
system.use(loggingMiddleware);crux-cli add bridge
Sets up cross-tab or cross-worker actor communication.
npx @hotreloads/crux-cli add bridgePrompts for:
- Transport — BroadcastChannel (cross-tab) or Custom (implement your own)
- Role — Host (actors live here) or Client (connects to host)
Generates the bridge setup file in your actors directory with usage examples.
crux-cli enhance <actor>
Upgrades an existing actor with advanced crux features.
# Add persistence (auto-save retained topics to localStorage)
npx @hotreloads/crux-cli enhance payments --add-persistence
# Add validation (Zod schema validation on ask payloads)
npx @hotreloads/crux-cli enhance payments --add-validation--add-persistence changes the actor to extend PersistentActor and adds a setupPersistence() call in init().
--add-validation changes the actor to extend ValidatedActor and guides you to define Zod schemas.
If both flags are passed, the CLI warns about the base class conflict and prompts you to choose which to use as the primary approach.
crux-cli list
Shows all registered actors and their features.
npx @hotreloads/crux-cli list Registered Actors:
┌──────────────────┬─────────────────┬──────────┐
│ Address │ Class │ Features │
├──────────────────┼─────────────────┼──────────┤
│ auth_actor │ AuthActor │ - │
│ payments_actor │ PaymentsActor │ persist │
└──────────────────┴─────────────────┴──────────┘Configuration
The CLI stores project configuration in crux.config.json at your project root:
{
"framework": "react-vite",
"actorsDir": "src/actors",
"packageManager": "npm",
"devtools": true,
"middleware": ["logging"],
"bridge": null
}All commands (except init) read this file. If it's missing, they'll tell you to run init first.
Error Handling
| Scenario | What happens |
|---|---|
| No package.json in cwd | Error: "Not a Node.js project." |
| crux.config.json missing | Error: "Run npx @hotreloads/crux-cli init first." |
| Actor name already exists | Error: "Actor already exists. Use a different name." |
| Auto-wiring can't find expected code | Warning with exact manual instructions |
| @hotreloads/crux not installed | init installs it; other commands warn |
How It Works Under the Hood
- File generation uses string templates — simple, debuggable, zero dependencies
- Auto-wiring uses ts-morph for AST-based TypeScript file modification — finds the right insertion point in your code (import statements, object literals, function bodies) and adds exactly what's needed without breaking formatting or existing code
- Framework detection reads your
package.jsondependencies and devDependencies - Package manager detection checks for lockfiles (
package-lock.json,yarn.lock,pnpm-lock.yaml,bun.lockb)
License
MIT
Built by Hotreloads Digital Private Limited
Looking for a technical partner? Contact [email protected]
