From 4e202afc6acf1bad5041b7e83364457887493ce7 Mon Sep 17 00:00:00 2001 From: Mathieu2301 <21021423+Mathieu2301@users.noreply.github.com> Date: Sat, 3 Oct 2026 05:06:22 +0000 Subject: [PATCH] fix(cli): make command help safe and align app-first defaults --- packages/cli/README.md | 7 +++++++ packages/cli/package.json | 4 ++-- packages/cli/src/main.ts | 17 ++++++++++++----- packages/cli/src/version.ts | 2 +- packages/cli/test/main.test.ts | 30 +++++++++++++++++++++++++++++- 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index 7b8fb81..9ccdc94 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -295,3 +295,10 @@ $ miakapp publish --json Alpha, tracking Miakapp 4. The package is `private` until the control plane it talks to is deployed; publishing it to npm is a deliberate, separate step. + +### Command help + +Run `miakapp help` or append `--help` / `-h` to any command, including +`miakapp pair --help`. Help never prompts for a code or executes the command. +`miakapp init` defaults to a whole-house app at `dist/app.js`; use +`--kind component` for a semantic component at `dist/component.js`. diff --git a/packages/cli/package.json b/packages/cli/package.json index 9a5fc0a..d9c1a4f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@miakapp/cli", - "version": "4.0.0-alpha.6", - "description": "Agent-first Miakapp command line: check, build, publish, activate and roll back home components", + "version": "4.0.0-alpha.7", + "description": "Agent-first Miakapp command line: check, build, publish, activate and roll back home interfaces", "type": "module", "engines": { "bun": ">=1.2.23", diff --git a/packages/cli/src/main.ts b/packages/cli/src/main.ts index 9974656..182d595 100644 --- a/packages/cli/src/main.ts +++ b/packages/cli/src/main.ts @@ -129,7 +129,7 @@ export interface Invocation { readonly positional: readonly string[]; } -const USAGE = `miakapp ${CLI_VERSION} — build, publish and roll back a Miakapp home component +const USAGE = `miakapp ${CLI_VERSION} — build, publish and roll back a Miakapp home interface Usage miakapp [options] @@ -156,6 +156,7 @@ Commands version Print the CLI version Common options + --help, -h Print this guide without running the command --json Print one machine-readable object on stdout --project Start the ${PROJECT_FILE} search here (default: cwd) --context Use this stored context (status, publish, activate, @@ -172,7 +173,7 @@ pair options publish options --expected-generation Generation the pointer holds (default: read live) --generation Generation to publish (default: expected + 1) - --release Override component.release from ${PROJECT_FILE} + --release Override app.release or component.release from ${PROJECT_FILE} activate / rollback options --sha256 Finalized artifact digest (required) @@ -193,7 +194,9 @@ agent-pack options init options --home Home ID (default: from the selected context) --control-plane Control-plane issuer (default: from the context) - --artifact Built artifact path (default: dist/component.js) + --kind Interface kind (default: app) + --artifact Built artifact path (default: dist/app.js for app, + dist/component.js for component) --release Initial release name (default: 0.1.0) Credentials, highest precedence first @@ -213,7 +216,7 @@ Exit codes 4 authorization 5 contract 6 conflict 7 unknown outcome `; -const GLOBAL_FLAGS = ['json'] as const; +const GLOBAL_FLAGS = ['json', 'help'] as const; const GLOBAL_OPTIONS = ['project'] as const; /** Exported so the MCP surface can be proved to expose every option, and no other. */ @@ -265,6 +268,10 @@ export function parseArguments(argv: readonly string[]): Invocation { for (let index = 1; index < argv.length; index += 1) { const argument = argv[index] as string; + if (argument === '-h') { + flags.add('help'); + continue; + } if (!argument.startsWith('--')) { positional.push(argument); continue; @@ -296,7 +303,7 @@ export function parseArguments(argv: readonly string[]): Invocation { options.set(name, value); index += 1; } - return { command, options, flags, positional }; + return { command: flags.has('help') ? 'help' : command, options, flags, positional }; } function requiredOption(invocation: Invocation, name: string): string { diff --git a/packages/cli/src/version.ts b/packages/cli/src/version.ts index 50aea9f..781acc3 100644 --- a/packages/cli/src/version.ts +++ b/packages/cli/src/version.ts @@ -12,4 +12,4 @@ */ export const PACKAGE_NAME = '@miakapp/cli'; -export const CLI_VERSION = '4.0.0-alpha.6'; +export const CLI_VERSION = '4.0.0-alpha.7'; diff --git a/packages/cli/test/main.test.ts b/packages/cli/test/main.test.ts index 50daf4f..9df5da3 100644 --- a/packages/cli/test/main.test.ts +++ b/packages/cli/test/main.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'bun:test'; import { EXIT_CODE } from '../src/errors.js'; -import { HOME_KEY_VARIABLE, guideAssetPath, parseArguments, run } from '../src/main.js'; +import { COMMAND_OPTIONS, HOME_KEY_VARIABLE, guideAssetPath, parseArguments, run } from '../src/main.js'; import { CLI_VERSION } from '../src/version.js'; import { digestOf, fakeControlPlane, homeKey } from './support/control-plane.js'; import { @@ -45,6 +45,34 @@ describe('argument parsing', () => { }); describe('offline commands', () => { + test('every command supports help without reading secrets, project files or the network', async () => { + for (const command of Object.keys(COMMAND_OPTIONS)) { + for (const flag of ['--help', '-h']) { + const files = new MemoryFiles(); + const host = testHost({ files, fetch: async () => { throw new Error('network used'); } }); + let secretReads = 0; + expect(await run([command, flag, '--json'], { + ...host, + readSecret: async () => { secretReads += 1; throw new Error('secret prompt used'); }, + })).toBe(EXIT_CODE.success); + expect(host.json()['usage']).toContain('--kind '); + expect(host.json()['usage']).toContain('default: dist/app.js for app'); + expect(host.stderr()).toBe(''); + expect(secretReads).toBe(0); + expect(files.entries.size).toBe(0); + } + } + }); + + test('context removal help preserves existing project files', async () => { + const files = standardProject(); + const before = [...files.entries]; + const host = testHost({ files }); + expect(await run(['context', 'remove', 'home', '--help'], host)).toBe(EXIT_CODE.success); + expect([...files.entries]).toEqual(before); + expect(host.stdout()).toContain('Commands'); + }); + test('version prints the package version', async () => { const host = testHost(); expect(await run(['version'], host)).toBe(EXIT_CODE.success);