Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
4 changes: 2 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
17 changes: 12 additions & 5 deletions packages/cli/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <command> [options]
Expand All @@ -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 <dir> Start the ${PROJECT_FILE} search here (default: cwd)
--context <name> Use this stored context (status, publish, activate,
Expand All @@ -172,7 +173,7 @@ pair options
publish options
--expected-generation <n> Generation the pointer holds (default: read live)
--generation <n> Generation to publish (default: expected + 1)
--release <name> Override component.release from ${PROJECT_FILE}
--release <name> Override app.release or component.release from ${PROJECT_FILE}

activate / rollback options
--sha256 <digest> Finalized artifact digest (required)
Expand All @@ -193,7 +194,9 @@ agent-pack options
init options
--home <homeId> Home ID (default: from the selected context)
--control-plane <https url> Control-plane issuer (default: from the context)
--artifact <path> Built artifact path (default: dist/component.js)
--kind <app|component> Interface kind (default: app)
--artifact <path> Built artifact path (default: dist/app.js for app,
dist/component.js for component)
--release <name> Initial release name (default: 0.1.0)

Credentials, highest precedence first
Expand All @@ -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. */
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
30 changes: 29 additions & 1 deletion packages/cli/test/main.test.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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 <app|component>');
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);
Expand Down
Loading