Background
When an unknown command is parsed, clapp emits only the generic `command:*` event:
```js
// dist/index.js:1250-1256
if (!this.matchedCommand && this.args[0]) {
this.emit('command:');
const hasWildcardListener = this.listenerCount('command:') > 0;
if (!hasWildcardListener) {
this.showCommandNotFound(this.args[0]);
}
}
```
This means `cli.on(':', handler)` is dead — the literal event `:` is never emitted. Historically (CAC v6-style behavior) listeners like `cli.on('migrate:*', ...)` would fire for any unknown subcommand of `migrate`, letting commands provide their own "did-you-mean" or contextual error path.
This blocks Stacks framework default templates (see stacksjs/stacks#893): `app/Commands/Inspire.ts` and `app/Listeners/Console.ts` both register `cli.on('inspire:*', ...)` expecting it to fire on `buddy inspire:bogus` — it doesn't. The user just sees the generic "Command not found" output.
Proposed behavior
When emitting for an unknown command:
```js
const arg = this.args[0]
this.emit('command:*', arg)
if (typeof arg === 'string' && arg.includes(':')) {
const prefix = arg.slice(0, arg.indexOf(':'))
this.emit(`${prefix}:*`, arg)
}
const hasWildcardListener =
this.listenerCount('command:') > 0 ||
(typeof arg === 'string' && arg.includes(':') && this.listenerCount(`${arg.slice(0, arg.indexOf(':'))}:`) > 0)
if (!hasWildcardListener) {
this.showCommandNotFound(this.args[0])
}
```
Two small changes from current:
- Pass the unknown-command string as a payload to `command:*` (today's emit has no payload, which is also a footgun — handlers can't see which command was unknown without reading `cli.args`).
- When the unknown command contains a colon, also emit `${prefix}:*` with the same payload, and count those listeners toward the "someone handled it" check so we don't show the default not-found message.
Acceptance
Once this lands, the Stacks-side fix on #893 is a no-op — the existing templates start working.
Background
When an unknown command is parsed, clapp emits only the generic `command:*` event:
```js
// dist/index.js:1250-1256
if (!this.matchedCommand && this.args[0]) {
this.emit('command:');
const hasWildcardListener = this.listenerCount('command:') > 0;
if (!hasWildcardListener) {
this.showCommandNotFound(this.args[0]);
}
}
```
This means `cli.on(':', handler)` is dead — the literal event `:` is never emitted. Historically (CAC v6-style behavior) listeners like `cli.on('migrate:*', ...)` would fire for any unknown subcommand of `migrate`, letting commands provide their own "did-you-mean" or contextual error path.
This blocks Stacks framework default templates (see stacksjs/stacks#893): `app/Commands/Inspire.ts` and `app/Listeners/Console.ts` both register `cli.on('inspire:*', ...)` expecting it to fire on `buddy inspire:bogus` — it doesn't. The user just sees the generic "Command not found" output.
Proposed behavior
When emitting for an unknown command:
```js
const arg = this.args[0]
this.emit('command:*', arg)
if (typeof arg === 'string' && arg.includes(':')) {
const prefix = arg.slice(0, arg.indexOf(':'))
this.emit(`${prefix}:*`, arg)
}
const hasWildcardListener =
this.listenerCount('command:') > 0 ||
(typeof arg === 'string' && arg.includes(':') && this.listenerCount(`${arg.slice(0, arg.indexOf(':'))}:`) > 0)
if (!hasWildcardListener) {
this.showCommandNotFound(this.args[0])
}
```
Two small changes from current:
Acceptance
Once this lands, the Stacks-side fix on #893 is a no-op — the existing templates start working.