{"id":"mrkirdid/kmdr","name":"kmdr","scope":"mrkirdid","platform":"roblox","description":"A strictly-typed, modern command console for Roblox. Reimagined fork of evaera's Cmdr.","version":"0.1.0","latest":"0.1.0","versions":["0.1.0"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{"elttob/fusion":{"version":"^0.3.0","alias":"Fusion"}},"integrity":"3722657396d735f9af1a193bbeb8faf2357b02ba5d1057417ad7ccad592ec187","likes":0,"downloads":0,"install":"forest install mrkirdid/kmdr","url":"https://forest.dev/p/roblox/mrkirdid/kmdr","files":"https://api.forest.dev/ai/package/roblox/mrkirdid/kmdr/files","readme":"# Kmdr\n\nA strictly-typed, modern command console for Roblox — a ground-up reimagining of\n[evaera's Cmdr](https://github.com/evaera/Cmdr) built for the new Luau type solver\nand a command bar that behaves like a modern command palette.\n\n```luau\n-- A command is pure data in a shared module…\nlocal teleport = Kmdr.defineCommand({\n\tname = \"teleport\",\n\taliases = { \"tp\" },\n\tdescription = \"Teleport a player to another player.\",\n\tgroup = \"admin\",\n\targs = {\n\t\ttarget = Kmdr.types.player(\"Player to teleport\"),\n\t\tdestination = Kmdr.types.player(\"Where to go\"):optional(),\n\t},\n})\n\n-- …and its server callback gets fully-typed args, derived by a type function.\nserver.register(Kmdr.implement(teleport, function(ctx, args)\n\t-- args : { read target: Player, read destination: Player? }   ← inferred!\n\tlocal destination = args.destination or ctx.executor\n\t...\n\treturn `Teleported {args.target.Name}.`\nend))\n```\n\n## Why not just Cmdr?\n\nCmdr is excellent and battle-tested, but it predates typed Luau: stringly-typed\ndefinitions, a `FooServer.luau` filename convention for server code, runtime script\nreparenting, an imperative 2018 UI with zero animation, and prefix-only autocomplete\nthat works only at the end of the line. Kmdr keeps the mental model (registry, types,\ndispatcher) and rebuilds everything on modern foundations:\n\n| | Cmdr | Kmdr |\n|---|---|---|\n| Typing | none (`--!strict` count: 0) | strict everywhere; `run` args **derived** via user-defined type functions |\n| Server impls | `xServer.luau` name magic | explicit `Kmdr.implement(def, fn)` |\n| Replication | reparents your ModuleScripts | serialized metadata sync; defs are pure data |\n| Security | warns if no BeforeRun hook | blocks by default until a guard is registered |\n| Autocomplete | prefix-only, end-of-line only | fuzzy, cursor-aware (edit arg 2 mid-line), quoted-aware |\n| History | plain up/down | prefix-filtered up/down + fish-style ghost text |\n| Editing | native TextBox only | Ctrl+Backspace, Ctrl+←/→, Ctrl+Shift+←/→, Ctrl+U |\n| Feedback | error after you run | live inline validation + signature help while typing |\n| UI | imperative `Instance.new`, no motion | Fusion 0.3; critically-damped springs, linear fades, typewriter output |\n\n## Install\n\n`wally.toml`:\n\n```toml\n[dependencies]\nKmdr = \"mrkirdid/kmdr@0.1.0\"\n```\n\nThen `wally install` and (for types across the wally link) `wally-package-types --sourcemap sourcemap.json Packages/`.\n\n## Bootstrap\n\n```luau\n-- ServerScriptService (Script)\nlocal Kmdr = require(ReplicatedStorage.Packages.Kmdr)\nlocal server = Kmdr.server()\n\nserver.addGuard(function(ctx)\n\t-- return a string to deny, nil to allow\n\tif ctx.group == \"admin\" and not isAdmin(ctx.executor) then\n\t\treturn \"Admins only\"\n\tend\n\treturn nil\nend)\n\nserver.registerDefaults()                    -- built-in commands\nserver.registerFolder(ServerStorage.Commands) -- your own\n\n-- StarterPlayerScripts (LocalScript)\nlocal Kmdr = require(ReplicatedStorage.Packages.Kmdr)\nlocal client = Kmdr.client({ placeLabel = \"mygame\" })\nclient.registerDefaults()\n```\n\nPress **F2**. Outside Studio, nothing with a server implementation runs until you add\na guard — that's deliberate.\n\n## The command bar\n\n- **Fuzzy matching** — `tpp` finds `teleport`; exact prefixes always rank first.\n- **Cursor-aware completion** — arrow back into the middle of the line and edit any\n  argument; only the token under the caret is replaced (Tab accepts).\n- **Live validation** — the offending token tints red and the reason shows under the\n  bar, before you ever press Enter. Signature help highlights the argument you're on.\n- **History** — Up/Down cycles history filtered by what you've typed; a dim\n  fish-style ghost of the best history match trails the caret (Right/End accepts).\n- **Word-level editing** — Ctrl+Backspace, Ctrl+←/→, Ctrl+Shift+←/→, Ctrl+U.\n- **Motion** — critically-damped springs for layout, linear fades for transparency,\n  per-letter reveals for accent text, fast typewriter output lines.\n\n## Core concepts\n\n### Argument types\n\n```luau\nlocal vibe = Kmdr.defineType({\n\tname = \"vibe\",\n\tresolve = function(text, ctx)\n\t\tlocal match = VIBES[string.lower(text)]\n\t\tif match then\n\t\t\treturn Kmdr.ok(match)\n\t\tend\n\t\treturn Kmdr.err(`'{text}' is not a vibe`, Kmdr.util.fuzzyFilter(text, VIBE_NAMES))\n\tend,\n})\n```\n\nOne `resolve` function is validation, autocomplete *and* parsing — it runs on every\nkeystroke (never yield in it) and once on submit. `Kmdr.listOf(handle)` derives\ncomma-list types; `Kmdr.enumOf(name, values)` derives enums (typed as the singleton\nunion of the values). ~30 built-ins live in `Kmdr.types`.\n\n### Guards, not hooks\n\n`server.addGuard(fn, priority?)` — first guard to return a string denies with that\nmessage. `ctx` carries `name`, `group`, `permission`, `executor`, `argValues`.\n`server.onRan(fn)` observes completed runs (logging). Client guards exist for fast\nlocal UX; the server always re-checks.\n\n### Definitions are data\n\nA `CommandDefinition` contains no functions, so it can live in ReplicatedStorage and\nbe registered on both realms. The server syncs metadata for its commands to clients,\nso autocomplete knows server commands without replicating any server code. Callbacks\nattach at registration: `server.register(Kmdr.implement(def, fn))` /\n`client.register(Kmdr.implement(def, fn))`.\n\n## Strictness notes\n\n- The engine and public API are `--!strict` under the **new type solver** (set\n  `Workspace.UseNewLuauTypeSolver = Enabled`, and `luau-lsp.fflags.enableNewSolver`\n  in your editor for the full experience — including `args` inference in callbacks).\n- The Fusion-facing interface layer runs `--!nonstrict` until Fusion ships\n  new-solver-ready types (its `UsedAs<T>` union defeats generic inference today).\n\n## License\n\nMIT — see [LICENSE](LICENSE). Derived from Cmdr, © 2018 Eryn L. K.\nDesign rationale in [DESIGN.md](DESIGN.md).\n","readmeTruncated":false}