{"_id":"@bunizao/cli-kit","_rev":"5-e6dc1edfd90ded9948cbe68561a9bf4b","name":"@bunizao/cli-kit","dist-tags":{"latest":"0.6.0"},"versions":{"0.1.0":{"name":"@bunizao/cli-kit","version":"0.1.0","license":"MIT","_id":"@bunizao/cli-kit@0.1.0","maintainers":[{"name":"bunizao","email":"hu@tuu.cat"}],"homepage":"https://github.com/bunizao/cli-kit#readme","bugs":{"url":"https://github.com/bunizao/cli-kit/issues"},"dist":{"shasum":"c200cb5df3faf9e4d224125ef5ee8755c16746e4","tarball":"https://registry.npmjs.org/@bunizao/cli-kit/-/cli-kit-0.1.0.tgz","fileCount":17,"integrity":"sha512-Ib4vhbTZ8bUh3Kx75DV5yp7gNpiuVJYIx5kD4FxIxBUgIOEAbAOUVumjV/azKCBLq5QZlBWgdmWMC+S1+j6YTw==","signatures":[{"sig":"MEUCIQCHr5VwhLyyL8RakutGxuEDnbwqZHTdWF204plkFyjz4gIgeHQAxpo3/r14YbRT0JiMlUDY9YCsXNziy8/jRDs3wkk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":20832},"type":"module","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"095c8b0384c13fe8f7f230148ad2416ef0578d96","scripts":{"test":"npm run build && node --test test/*.test.js","build":"tsc -p tsconfig.json","check":"tsc -p tsconfig.json --noEmit","prepack":"npm test","test:conformance":"npm --prefix conformance test"},"_npmUser":{"name":"bunizao","email":"hu@tuu.cat"},"repository":{"url":"git+https://github.com/bunizao/cli-kit.git","type":"git"},"_npmVersion":"11.14.1","description":"Shared command-line contract for Bunizao CLI tools","directories":{},"_nodeVersion":"22.12.0","dependencies":{"yaml":"^2.8.0","commander":"^13.1.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.3","@types/node":"^22.15.0"},"_npmOperationalInternal":{"tmp":"tmp/cli-kit_0.1.0_1785263570945_0.4090264885628987","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@bunizao/cli-kit","version":"0.2.0","license":"MIT","_id":"@bunizao/cli-kit@0.2.0","maintainers":[{"name":"bunizao","email":"hu@tuu.cat"}],"homepage":"https://github.com/bunizao/cli-kit#readme","bugs":{"url":"https://github.com/bunizao/cli-kit/issues"},"dist":{"shasum":"9fc23a3b9b474f4698d3f5c3909b796a238d5755","tarball":"https://registry.npmjs.org/@bunizao/cli-kit/-/cli-kit-0.2.0.tgz","fileCount":17,"integrity":"sha512-r09mbM4wbNQcgyc+IjGp7Ypfx/c0jjQHEBqInvzu210v/kOyaLSzSye7HNTlm5Bvcs+97SzVE2XwxccHXHr13w==","signatures":[{"sig":"MEQCIANtm0uZCLsFaquHVFwx2HBrr4eToceMhefLoasPpwDTAiAbJvH3lS2bJR2KWaDOtkh5VEOMYIViujPgEsdHqkou/g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":23232},"type":"module","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"dc240b87a9ef4f446eb534265e8bf2a7e0080251","scripts":{"test":"npm run build && node --test test/*.test.js","build":"tsc -p tsconfig.json","check":"tsc -p tsconfig.json --noEmit","prepack":"npm test","test:conformance":"npm --prefix conformance test"},"_npmUser":{"name":"bunizao","email":"hu@tuu.cat"},"repository":{"url":"git+https://github.com/bunizao/cli-kit.git","type":"git"},"_npmVersion":"11.14.1","description":"Shared command-line contract for Bunizao CLI tools","directories":{},"_nodeVersion":"22.12.0","dependencies":{"yaml":"^2.8.0","commander":"^13.1.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.3","@types/node":"^22.15.0"},"_npmOperationalInternal":{"tmp":"tmp/cli-kit_0.2.0_1789489452541_0.22031903942909636","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@bunizao/cli-kit","version":"0.5.0","license":"MIT","_id":"@bunizao/cli-kit@0.5.0","maintainers":[{"name":"bunizao","email":"hu@tuu.cat"}],"homepage":"https://github.com/bunizao/cli-kit#readme","bugs":{"url":"https://github.com/bunizao/cli-kit/issues"},"dist":{"shasum":"67e84a6280b502613dd7462bf16155358eac00fc","tarball":"https://registry.npmjs.org/@bunizao/cli-kit/-/cli-kit-0.5.0.tgz","fileCount":29,"integrity":"sha512-11Wu/3femIiT83twviNr51XjVj4gJWCEokld4B6pKpYH8fhjc1PLCgcpk1nJEo8g4ALo5DP7qYlorAOp8DV68A==","signatures":[{"sig":"MEUCIEPE/fPKqd3OFPfpjaUoX530iQsRl6GLeM3aXo8QqFCvAiEAmPcf+chk9NGIvqaA1NmhJO1Qgm8HkdF4Zmz7B0dAjU0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEQCIE+7nzX0qqPXeJImgEDv1sCi2Tou+JLoXZjq2tYchN9wAiAvsJ6TP64l1LBUCT69SOIoKwkfZ3Xe2QKvXeurwwJ+Hw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bunizao%2fcli-kit@0.5.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":66107},"type":"module","engines":{"node":">=20.12"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"2037000e48d9b0f105fdd57dbad57c63a2e8b92f","scripts":{"test":"npm run build && node --test test/*.test.js","build":"tsc -p tsconfig.json","check":"tsc -p tsconfig.json --noEmit","prepack":"npm test","test:conformance":"npm --prefix conformance test"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:62633d31-4888-4653-b665-97f6e50be777"}},"repository":{"url":"git+https://github.com/bunizao/cli-kit.git","type":"git"},"_npmVersion":"11.19.0","description":"Shared command-line contract for Bunizao CLI tools","directories":{},"_nodeVersion":"24.20.0","dependencies":{"yaml":"^2.8.0","commander":"^13.1.0","@clack/prompts":"^1.8.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.3","@types/node":"^22.15.0"},"_npmOperationalInternal":{"tmp":"tmp/cli-kit_0.5.0_1789876963219_0.7822703452963831","host":"s3://npm-registry-packages-npm-production"}},"0.5.1":{"name":"@bunizao/cli-kit","version":"0.5.1","license":"MIT","_id":"@bunizao/cli-kit@0.5.1","maintainers":[{"name":"bunizao","email":"hu@tuu.cat"}],"homepage":"https://github.com/bunizao/cli-kit#readme","bugs":{"url":"https://github.com/bunizao/cli-kit/issues"},"dist":{"shasum":"ae114fc462b7bb783b4052e99ed5bcd0aaa541cf","tarball":"https://registry.npmjs.org/@bunizao/cli-kit/-/cli-kit-0.5.1.tgz","fileCount":29,"integrity":"sha512-fUdYUFNgLuwA8BcvktMaGAsUlu9zZJAoU++YCl4HG8UwXtbl2eRDvkY4SNdwBIEOjZVMHn5xpTpuuzs8klLWSQ==","signatures":[{"sig":"MEYCIQCNf54JL6D5Tt/TmooM2j25moVpgrguC8Ty5L7L0EW2ygIhAPhBDp2k3SMvAFALy5YdgGkjDUVDmDjtwaxRuqVxUUvo","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQD74rurBcd8Lnv/K4xCOxXHTUOAmI4HxkoupBr9snnIsQIgSlJY3fG0j4FSrHbGzZGVfkwQTzoBRDXcqywPBe4D2Zs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bunizao%2fcli-kit@0.5.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":66234},"type":"module","engines":{"node":">=20.12"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"acfa5b7cdabeb294d4a877c9562fed725834297b","scripts":{"test":"npm run build && node --test test/*.test.js","build":"tsc -p tsconfig.json","check":"tsc -p tsconfig.json --noEmit","prepack":"npm test","test:conformance":"npm --prefix conformance test"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:62633d31-4888-4653-b665-97f6e50be777"}},"repository":{"url":"git+https://github.com/bunizao/cli-kit.git","type":"git"},"_npmVersion":"11.19.0","description":"Shared command-line contract for Bunizao CLI tools","directories":{},"_nodeVersion":"24.20.0","dependencies":{"yaml":"^2.8.0","commander":"^13.1.0","@clack/prompts":"^1.8.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.3","@types/node":"^22.15.0"},"_npmOperationalInternal":{"tmp":"tmp/cli-kit_0.5.1_1790189890115_0.33933896941250086","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"_id":"@bunizao/cli-kit@0.6.0","bugs":{"url":"https://github.com/bunizao/cli-kit/issues"},"dist":{"shasum":"b2b629b549acce15bfa6f5ad8f76911b32a665b0","tarball":"https://registry.npmjs.org/@bunizao/cli-kit/-/cli-kit-0.6.0.tgz","fileCount":29,"integrity":"sha512-ZyAI0Z4AMG7iNJKnFcquGKaFVNE35CVwCBnXxSbZHVhH2eA6Aycgbt8AEUpnUXwelgwrWxh6NGL8HzA5m+op9A==","signatures":[{"sig":"MEUCIQDcCUU0HdLRxTE42pNx+v0ABXvDm9J90wgRgrwoq/xZWgIgGKTjellCuvGTF9xdheu3Ds4JBPIFBhKED1JF0Mlg7SQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCua47OIYgOxnws041yHXE0P/H0PIcFRyPI30pmZQmkqAIhAJ717ie/unX0mpZycmAOxrNOcnoqImqG36UfrRMYrqnw"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bunizao%2fcli-kit@0.6.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":71631},"name":"@bunizao/cli-kit","type":"module","engines":{"node":">=20.12"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"3e481fb23d1110450298b20b7069bdd92720ee00","license":"MIT","scripts":{"test":"npm run build && node --test test/*.test.js","build":"tsc -p tsconfig.json","check":"tsc -p tsconfig.json --noEmit","prepack":"npm test","test:conformance":"npm --prefix conformance test"},"version":"0.6.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:62633d31-4888-4653-b665-97f6e50be777"}},"homepage":"https://github.com/bunizao/cli-kit#readme","repository":{"url":"git+https://github.com/bunizao/cli-kit.git","type":"git"},"_npmVersion":"11.19.0","description":"Shared command-line contract for Bunizao CLI tools","directories":{},"maintainers":[{"name":"bunizao","email":"hu@tuu.cat"}],"_nodeVersion":"24.21.0","dependencies":{"yaml":"^2.8.0","commander":"^13.1.0","@clack/prompts":"^1.8.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.3","@types/node":"^22.15.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/cli-kit_0.6.0_1790500009134_0.8861266329752444"}}},"time":{"created":"2026-07-28T18:32:50.711Z","modified":"2026-09-27T09:06:49.625Z","0.1.0":"2026-07-28T18:32:51.096Z","0.2.0":"2026-09-15T16:24:12.730Z","0.5.0":"2026-09-20T04:02:43.323Z","0.5.1":"2026-09-23T18:58:10.264Z","0.6.0":"2026-09-27T09:06:49.235Z"},"bugs":{"url":"https://github.com/bunizao/cli-kit/issues"},"license":"MIT","homepage":"https://github.com/bunizao/cli-kit#readme","repository":{"url":"git+https://github.com/bunizao/cli-kit.git","type":"git"},"description":"Shared command-line contract for Bunizao CLI tools","maintainers":[{"name":"bunizao","email":"hu@tuu.cat"}],"readme":"# @bunizao/cli-kit\n\nSmall shared primitives for the `ontrack`, `moodle`, and `edstem` command-line tools. The package\nowns their stable command contract: verbs, error codes, exit codes, output formats, mutation\nconfirmation, and command self-description.\n\n## Install\n\n```sh\nnpm install https://codeload.github.com/bunizao/cli-kit/tar.gz/refs/tags/v0.1.0 commander\n```\n\nAfter the registry release, consumers can switch to the versioned package:\n\n```sh\nnpm install @bunizao/cli-kit commander\n```\n\nNode.js 18 or newer is supported. Individual CLIs may require a newer runtime.\n\n## Program setup\n\n```ts\nimport {\n  commandsJson,\n  createProgram,\n  insertDefaultVerb,\n  mutating,\n  type NounSpec,\n} from \"@bunizao/cli-kit\";\n\nconst program = createProgram({\n  name: \"example\",\n  version: \"1.0.0\",\n  description: \"Example CLI\",\n});\n\nconst units = program.command(\"units\").aliases([\"courses\", \"projects\"]);\nunits.command(\"list\").action(listUnits);\nunits.command(\"show <unit>\").action(showUnit);\nmutating(units.command(\"set <unit> <state>\")).action(setUnitState);\n\nconst nouns: readonly NounSpec[] = [\n  {\n    name: \"units\",\n    aliases: [\"courses\", \"projects\"],\n    verbs: [\"list\", \"show\", \"set\"],\n    defaultByArity: { 0: \"list\", 1: \"show\" },\n    valueFlags: [],\n  },\n];\n\nconst args = insertDefaultVerb(process.argv.slice(2), nouns);\nawait program.parseAsync(args, { from: \"user\" });\n```\n\n`insertDefaultVerb` accepts user arguments, not the Node executable and script prefix. It is a\npure transform and does not mutate the provided array.\n\nPrefer `parseWithPrompts` over calling `parseAsync` yourself. It builds the program, parses,\nand when a person at a terminal left out a trailing positional or a required option, asks for\nit and parses again with the answer appended. `moodle activities list` becomes a unit picker\ninstead of `missing required argument 'unit'`. Without a terminal the usage error is thrown\nunchanged, with the command's usage line and argument descriptions as its hint.\n\n```ts\nawait parseWithPrompts(() => buildProgram(), args, {\n  ui,\n  fillers: {\n    unit: async ({ ui }) => ui.select(\"Which unit?\", await unitChoices()),\n    task: async ({ provided, ui }) => ui.select(\"Which task?\", await taskChoices(provided.unit)),\n  },\n});\n```\n\nA filler is looked up by argument name; without one, an argument with `.choices()` becomes a\npicker and anything else a text prompt labelled with its description. Fillers see the\npositionals typed so far, including earlier answers, so a task picker can load the chosen\nunit. The factory is called once per round because Commander programs do not parse twice.\n\n## Help\n\n`createProgram` installs one help layout for the family: name and version, usage, the\ncommands, options, and a short \"Try\" list, coloured on a terminal and plain in a pipe.\n`--no-color`, `NO_COLOR` and `FORCE_COLOR` are honoured. Group top-level commands with\n`helpSection` the way `gh` does (core commands first, then the rest), add two or three\ninvocations with `examples` (text after two spaces and `#` renders as a comment), and\ngive the root a wordmark with `banner`; the art shows only to a person at a terminal.\n\n```ts\nhelpSection(program.command(\"submit\"), \"Core commands\");\nexamples(program, [\"example units\", \"example submit UNIT report.pdf  # asks before uploading\"]);\nbanner(program, EXAMPLE_WORDMARK);\n```\n\n## Theme\n\n`createTheme(enabled)` paints the roles every CLI's human output shares, so a person\nlearns once what each colour means: `key` (cyan) is something they can type back, such\nas a unit code or task number; `subject` (bold) is what they gave, such as files or a\nmessage; `target` (bold cyan) is where it goes; `dim` is a secondary fact; `status`\ncolours a word by its `toneOf`, the vocabulary learning sites share (\"overdue\" is\ndanger, \"graded\" success, \"not started\" muted, \"announcement\" accent). Pass the theme\nto `render` and a table gets a dim header, a keyed first column and toned status\ncolumns; pass `tones` for words the shared list would misread. `ui.banner(art, tagline)`\nopens an onboarding flow with the wordmark.\n\n`isInformationalExit(error)` is true for the help and version exits Commander throws under\n`exitOverride`, including a bare noun with no verb, so the run loop can return 0 for them.\n\nList option flags that consume a separate value in `valueFlags`. This lets the arity counter ignore\noption values when flags are interleaved with positionals. Boolean flags and `--flag=value` do not\nneed to be listed.\n\n## Output and errors\n\n`resolveFormat` applies explicit `--json`, `--yaml`, or `--table` flags, then defaults to `table`\nfor a TTY and `json` for a pipe. `render` serializes a value and can select top-level fields.\n`writeOutput` writes to stdout or a file and treats a closed stdout pipe as successful.\n\nCatch errors at the executable boundary and render them once:\n\n```ts\nconst format = resolveFormat(program.opts(), process.stdout.isTTY === true);\n\ntry {\n  await program.parseAsync(args, { from: \"user\" });\n} catch (error) {\n  const isNormalExit = typeof error === \"object\" && error && \"exitCode\" in error && error.exitCode === 0;\n  if (!isNormalExit) {\n    const reported = reportError(error, format);\n    process.stderr.write(reported.text);\n    process.exitCode = reported.exitCode;\n  }\n}\n```\n\nCommander error text is suppressed by `createProgram`, so the shared reporter is the only error\nrenderer. Help and version requests render normally, then throw Commander's zero-exit signal;\npreserve that status without reporting it. Usage failures throw for the boundary to report once.\n\n## Mutations\n\nCall `mutating(command)` for `send`, `submit`, `set`, and `mark-read` commands. The marker is\nincluded by `commandsJson`. Command actions call `confirm` before making an upstream request:\n\n```ts\nconst shouldApply = await confirm(\n  { summary: \"Set FIT1045 task 1.1 to complete\" },\n  { yes: options.yes, dryRun: options.dryRun, interactive: process.stdin.isTTY === true },\n);\n\nif (!shouldApply) return;\n```\n\nThe plan and prompt are written to stderr. A non-interactive mutation without `--yes` throws a\n`usage` error. A dry run prints its plan and returns `false`.\n\n## Command description\n\n`commandsJson(program)` returns the program metadata and full command tree. Every node includes\naliases, positional arguments, options, enum values, nested commands, and `mutating`. Domain\ncommands at noun depth must use a verb exported in `VERBS`; unsupported verbs throw. `auth` and\n`skills` are action groups rather than domain nouns and are not assigned a `verb` field.\n\nThe exit-code table, error vocabulary, and verb set are public versioned API. Changing one requires\na major package release.\n\nThe conformance suite is isolated under `conformance/`. Until all three CLIs have published their\nnormalized releases, missing binaries are reported as skipped tests. Its CI workflow installs the\npublished packages before running the suite.\n\n## Prompts and progress\n\n`createUi` gives every CLI the same interactive surface, drawn with `@clack/prompts` on\nstderr so `--json` output on stdout stays clean. It only prompts when both stdin and the\noutput are terminals; in a pipe, log calls print plain completed lines, spinners print\ntheir outcome, and every prompt throws a `usage` error that says what to pass instead.\n\n```ts\nconst ui = createUi();\nui.intro(\"moodle submit\");\nconst unit = await ui.select(\"Which unit?\", candidates.map((c) => ({ value: c.id, label: c.name, hint: c.code })));\nconst spin = ui.spinner();\nspin.start(\"Uploading 2 files\");\nspin.stop(\"Uploaded 2 files\");\nui.outro(\"Done\");\n```\n\n`select` switches to type-to-filter above `AUTOCOMPLETE_FROM` choices, or always with\n`{ search: true }`; every typed word must appear in the label or hint, in any order.\n`{ initial }` highlights a row. A multi-step picker passes `{ back: true }` and gets `BACK`\nwhen the person presses Escape. Ctrl+C inside a prompt throws a `cancelled` error (exit 130). `confirm(plan, options)` is built on the same\nlayer and keeps its contract: `--dry-run` prints the plan, `--yes` skips the question, a pipe\nwithout `--yes` is a usage error.\n\n## Human or agent\n\n`detectAudience({ stdin, stdout, env, format })` returns `\"human\"` only for a person at a\nterminal reading a table: both streams are TTYs, the format is not JSON or YAML, and none\nof `AGENT_ENV_VARS` (`CLI_AGENT`, `CLAUDECODE`, `CI`) is set. Everyone else is an agent and\ngets machine output, no prompts, and errors that name the flag to pass. `createUi` applies\nthe same environment rule on its own, so an agent that allocates a pty still never sees a\nprompt. Agents that run commands in a terminal should export `CLI_AGENT=1`.\n\n`ui.password` masks a secret; without a terminal it throws so the caller can point at a\n`--token-stdin` style flag instead. `ui.editor` collects several lines in `$VISUAL` or\n`$EDITOR` the way git collects a commit message, and throws without a terminal so the\ncaller can point at a `--body-file` style flag.\n","readmeFilename":"README.md"}