From 26b40d169bef2043feec49c036d22d54313a9ee8 Mon Sep 17 00:00:00 2001 From: Mathieu2301 <21021423+Mathieu2301@users.noreply.github.com> Date: Tue, 15 Sep 2026 16:29:45 +0200 Subject: [PATCH] feat(cli): make the agent guide directly readable --- bun.lock | 2 +- packages/cli/package.json | 2 +- packages/cli/src/main.ts | 40 ++++++++++++++++++++++++++++++++++ packages/cli/src/mcp.ts | 16 ++++++++++++++ packages/cli/src/version.ts | 2 +- packages/cli/test/main.test.ts | 23 +++++++++++++++++-- packages/cli/test/mcp.test.ts | 5 +++++ 7 files changed, 85 insertions(+), 5 deletions(-) diff --git a/bun.lock b/bun.lock index 9dbdb55..b088892 100644 --- a/bun.lock +++ b/bun.lock @@ -18,7 +18,7 @@ }, "packages/cli": { "name": "@miakapp/cli", - "version": "4.0.0-alpha.0", + "version": "4.0.0-alpha.1", "bin": { "miakapp": "./bin/miakapp.js", }, diff --git a/packages/cli/package.json b/packages/cli/package.json index 0e5c0a1..ab10dfe 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@miakapp/cli", - "version": "4.0.0-alpha.0", + "version": "4.0.0-alpha.1", "description": "Agent-first Miakapp command line: check, build, publish, activate and roll back home components", "type": "module", "engines": { diff --git a/packages/cli/src/main.ts b/packages/cli/src/main.ts index c3babd5..cd71178 100644 --- a/packages/cli/src/main.ts +++ b/packages/cli/src/main.ts @@ -84,6 +84,8 @@ export interface CommandResult { readonly summary: string; readonly fields: readonly Field[]; readonly json: Record; + /** Complete human-facing document for commands such as `docs start`. */ + readonly text?: string; } export interface Invocation { @@ -99,6 +101,7 @@ Usage miakapp [options] Commands + docs start Print the complete agent guide bundled with this CLI init Write ${PROJECT_FILE} in the current directory agent-pack Install the guide and the MCP wiring into a repository discover Inventory an existing Node-RED installation offline @@ -158,6 +161,7 @@ const GLOBAL_OPTIONS = ['project'] as const; /** Exported so the MCP surface can be proved to expose every option, and no other. */ export const COMMAND_OPTIONS: Record = { + docs: [], init: ['home', 'control-plane', 'artifact', 'release'], 'agent-pack': ['dir'], discover: ['flows'], @@ -445,6 +449,39 @@ export async function guideAssetPath(): Promise { return fileURLToPath(new URL('../assets/agent-guide.md', import.meta.url)); } +/** + * Gives an agent its complete starting contract without requiring a repository, + * a network request or an MCP client. The onboarding page can therefore hand a + * person one stable command and the installed CLI remains the source of truth. + */ +async function runDocs(host: CliHost, invocation: Invocation): Promise { + if (invocation.positional.length !== 1 || invocation.positional[0] !== 'start') { + throw usageError( + 'docs requires the topic start', + 'Run miakapp docs start to print the complete agent guide.', + ); + } + + const path = await guideAssetPath(); + let guide: string; + try { + const filesystem = await files(host); + guide = new TextDecoder('utf-8', { fatal: true }).decode(await filesystem.read(path)); + } catch { + throw projectError( + `The packaged guide is missing or unreadable at ${path}`, + 'Reinstall @miakapp/cli: the guide is bundled with the package and opens no network connection.', + ); + } + + return { + summary: 'Miakapp agent guide', + fields: [], + json: { topic: 'start', guide }, + text: guide.endsWith('\n') ? guide : `${guide}\n`, + }; +} + /** * Installs the agent pack. Like `discover`, it loads no project file: the * repository it prepares is usually one that has no V4 project yet. @@ -699,6 +736,8 @@ async function runUpload(host: CliHost, invocation: Invocation): Promise { switch (invocation.command) { + case 'docs': + return await runDocs(host, invocation); case 'init': return await runInit(host, invocation); case 'agent-pack': @@ -722,6 +761,7 @@ export async function dispatch(host: CliHost, invocation: Invocation): Promise { test('version prints the package version', async () => { const host = testHost(); expect(await run(['version'], host)).toBe(EXIT_CODE.success); - expect(host.stdout().trim()).toBe('4.0.0-alpha.0'); + expect(host.stdout().trim()).toBe(CLI_VERSION); + }); + + test('docs start prints the complete bundled guide without a project or network', async () => { + const files = new MemoryFiles({ + [await guideAssetPath()]: '# Miakapp agent guide\n\nStart here.\n', + }); + const host = testHost({ files, fetch: async () => { throw new Error('network used'); } }); + + expect(await run(['docs', 'start'], host)).toBe(EXIT_CODE.success); + expect(host.stdout()).toBe('# Miakapp agent guide\n\nStart here.\n'); + expect(host.stderr()).toBe(''); + }); + + test('docs rejects an unknown topic instead of printing the wrong contract', async () => { + const host = testHost(); + + expect(await run(['docs', 'publish'], host)).toBe(EXIT_CODE.usage); + expect(host.stderr()).toContain('miakapp docs start'); }); test('an unknown command exits with the usage code', async () => { diff --git a/packages/cli/test/mcp.test.ts b/packages/cli/test/mcp.test.ts index e08d6e5..6a40175 100644 --- a/packages/cli/test/mcp.test.ts +++ b/packages/cli/test/mcp.test.ts @@ -132,6 +132,11 @@ describe('argument translation', () => { .toEqual(['release', ARTIFACT_DIGEST]); }); + test('docs start is exposed as the same read-only positional command', () => { + expect(buildArgv(tool('miakapp_docs'), { topic: 'start' })) + .toEqual(['docs', 'start']); + }); + test('an invented argument is refused rather than dropped', () => { expect(() => buildArgv(tool('miakapp_check'), { force: true })).toThrow(/Unknown argument/); });