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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ if (homeKey === undefined) throw new Error('MIAKAPP_HOME_KEY is required');
const coordinator = createCoordinator({
name: 'home-assistant',
accessTokenProvider: createHomeKeyAccessTokenProvider({
exchangeEndpoint: 'https://control.miakapp.com/v1/access-tokens:exchange',
exchangeEndpoint: 'https://api.miakapp.com/v1/access-tokens:exchange',
homeKey,
}),
});
Expand Down
13 changes: 5 additions & 8 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ build a whole-house application with nothing but this package and Bun:
Prerequisites: Node.js ≥ 22.9 for the CLI, Bun ≥ 1.2.23 for `bun run build`
(TypeScript, from `bun install`, only for `typecheck`). The starter accepts
`--artifact` (a plain relative `.js` path), `--release`, `--home`,
`--control-plane` and `--context`; it cannot be combined with
`--kind component`.
`--control-plane` and `--context`.

It never overwrites anything: every target is checked first, and if any one
exists the command fails with exit 2 and writes nothing. Without `--starter`,
Expand Down Expand Up @@ -89,8 +88,7 @@ coordinator:
```

`app` declares only `state_read` and `call`; events, media and reserved
`miakapp.*` calls are unavailable. Use `miakapp init --kind component` for the
semantic component ABI instead. Choose exactly one of `app` and `component`.
`miakapp.*` calls are unavailable.

`requires` is the closed RFC 0002 capability object. Lists are de-duplicated and
sorted before they are bound into an upload capability, so the value the CLI
Expand Down Expand Up @@ -237,7 +235,7 @@ miakapp pair --code "$CODE" # allowed, but lands in shell history

| Option | Default |
| --- | --- |
| `--issuer <https url>` | `https://control.miakapp.com` |
| `--issuer <https url>` | `https://api.miakapp.com` |
| `--label <label>` | `miakapp-cli@<host name>`, shown in the owner's key list |
| `--name <context>` | the paired home ID (`-2`, `-3`… if taken) |

Expand Down Expand Up @@ -341,6 +339,5 @@ talks to is deployed; publishing it to npm is a deliberate, separate step.

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`; add
`--starter app` for the buildable starter, or use `--kind component` for a
semantic component at `dist/component.js`.
`miakapp init` writes a whole-house app at `dist/app.js`; add
`--starter app` for the buildable starter.
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@miakapp/cli",
"version": "4.0.0-alpha.15",
"version": "4.0.0",
"description": "Agent-first Miakapp command line: check, build, publish, activate and roll back home interfaces",
"type": "module",
"engines": {
Expand Down
38 changes: 5 additions & 33 deletions packages/cli/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,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 app.release or component.release from ${PROJECT_FILE}
--release <name> Override app.release from ${PROJECT_FILE}

activate / rollback options
--sha256 <digest> Finalized artifact digest (required)
Expand All @@ -203,9 +203,7 @@ 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)
--kind <app|component> Interface kind (default: app)
--artifact <path> Built artifact path (default: dist/app.js for app,
dist/component.js for component)
--artifact <path> Built artifact path (default: dist/app.js)
--release <name> Initial release name (default: 0.1.0)
--starter app Also write a self-contained app to edit: app/main.ts,
the app/miakapp.ts bridge, package.json (build and
Expand Down Expand Up @@ -239,7 +237,7 @@ export const COMMAND_OPTIONS: Record<string, readonly string[]> = {
docs: [],
pair: ['code', 'issuer', 'label', 'name'],
context: [],
init: ['home', 'control-plane', 'artifact', 'release', 'context', 'kind', 'starter'],
init: ['home', 'control-plane', 'artifact', 'release', 'context', 'starter'],
'agent-pack': ['dir'],
discover: ['flows'],
check: [],
Expand Down Expand Up @@ -625,10 +623,8 @@ function projectTemplate(fields: {
controlPlane: string;
artifact: string;
release: string;
kind: 'app' | 'component';
}): string {
if (fields.kind === 'app') {
return `schema: ${PROJECT_SCHEMA}
return `schema: ${PROJECT_SCHEMA}
home: ${fields.home}
control_plane: ${fields.controlPlane}

Expand All @@ -642,21 +638,6 @@ app:
requires:
state_read: []
call: []
`;
}
return `schema: ${PROJECT_SCHEMA}
home: ${fields.home}
control_plane: ${fields.controlPlane}

component:
artifact: ${fields.artifact}
release: ${fields.release}
requires:
state_read: []
event_subscribe: []
event_publish: []
call: []
presentation: []
`;
}

Expand All @@ -672,14 +653,7 @@ async function runInit(host: CliHost, invocation: Invocation): Promise<CommandRe
if (starter !== undefined && !isStarter(starter)) {
throw usageError(`--starter must be ${STARTERS.join(' or ')}, received ${starter}`);
}
const kind = invocation.options.get('kind') ?? 'app';
if (kind !== 'app' && kind !== 'component') {
throw usageError('--kind must be app (whole-house application, default) or component');
}
if (starter !== undefined && kind !== 'app') {
throw usageError('--starter app writes a whole-house application; it cannot be combined with --kind component');
}
const artifact = invocation.options.get('artifact') ?? (kind === 'app' ? 'dist/app.js' : 'dist/component.js');
const artifact = invocation.options.get('artifact') ?? 'dist/app.js';
if (starter !== undefined) validateStarterArtifact(artifact);

// Every collision is found before anything is written: a refused init leaves
Expand All @@ -703,7 +677,6 @@ async function runInit(host: CliHost, invocation: Invocation): Promise<CommandRe
controlPlane: target.controlPlane,
artifact,
release,
kind,
});
// Parsed before it is written, so init can never emit a file check rejects.
parseProject(root, source);
Expand Down Expand Up @@ -748,7 +721,6 @@ async function runInit(host: CliHost, invocation: Invocation): Promise<CommandRe
json: {
project: path,
schema: PROJECT_SCHEMA,
kind,
home_id: target.home,
control_plane: target.controlPlane,
context: target.context ?? null,
Expand Down
17 changes: 5 additions & 12 deletions packages/cli/src/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ export const TOOLS: readonly ToolDefinition[] = [
name: 'issuer',
type: 'string',
required: false,
description: 'Control plane to redeem at. Defaults to https://control.miakapp.com.',
description: 'Control plane to redeem at. Defaults to https://api.miakapp.com.',
},
{
name: 'label',
Expand Down Expand Up @@ -330,7 +330,7 @@ export const TOOLS: readonly ToolDefinition[] = [
description:
'Write miakapp.yaml in the project directory. Refuses to overwrite an existing one, so it '
+ 'is safe to call when unsure. home and control_plane default to the selected or current '
+ 'context, so after miakapp_pair no identifier needs typing. kind defaults to app: a '
+ 'context, so after miakapp_pair no identifier needs typing. It declares a '
+ 'whole-house application you draw yourself (any DOM, CSS, framework) bundled as one IIFE. '
+ 'Declares no requirements: grant them one at a time, as the interface earns them. With '
+ 'starter "app" it also writes a self-contained app to edit (app/main.ts, the app/miakapp.ts '
Expand All @@ -339,19 +339,12 @@ export const TOOLS: readonly ToolDefinition[] = [
+ 'shows only what the home shares and invents nothing: inventory the house and adapt it '
+ 'before publishing.',
args: [
{
name: 'kind',
type: 'string',
required: false,
description: 'app (whole-house application, default) or component (semantic tree).',
},
{
name: 'starter',
type: 'string',
required: false,
description:
'app: also write the buildable app starter. Omit to write miakapp.yaml only. Not '
+ 'combinable with kind component.',
'app: also write the buildable app starter. Omit to write miakapp.yaml only.',
},
{
name: 'home',
Expand All @@ -369,7 +362,7 @@ export const TOOLS: readonly ToolDefinition[] = [
name: 'artifact',
type: 'string',
required: false,
description: 'Built artifact path. Defaults to dist/app.js (app) or dist/component.js.',
description: 'Built artifact path. Defaults to dist/app.js.',
},
{
name: 'release',
Expand Down Expand Up @@ -443,7 +436,7 @@ export const TOOLS: readonly ToolDefinition[] = [
name: 'release',
type: 'string',
required: false,
description: 'Release name for this publication. Defaults to component.release.',
description: 'Release name for this publication. Defaults to app.release.',
},
PROJECT_ARGUMENT,
CONTEXT_ARGUMENT,
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/pairing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { boundedString, exactRecord, parseJson } from './internal/json.js';
import { isHomeId } from './internal/names.js';

export const PAIRING_PATH = '/v1/pairing/redeem';
export const DEFAULT_ISSUER = 'https://control.miakapp.com';
export const DEFAULT_ISSUER = 'https://api.miakapp.com';
export const PAIRING_PAGE = 'https://miakapp.com/pair';

const HOME_KEY = /^mhk1_([A-Za-z0-9_-]{22})_([A-Za-z0-9_-]{43})$/;
Expand Down
17 changes: 9 additions & 8 deletions packages/cli/src/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { projectError } from './errors.js';
import { canonicalHttpsUrl } from './internal/http.js';
import {
APP_ABI,
COMPONENT_ABI,
isHomeId,
isRelease,
type ReleaseAbi,
Expand Down Expand Up @@ -98,16 +97,18 @@ export function parseProject(root: string, source: string): Project {
const issuer = canonicalHttpsUrl(document.control_plane, 'control_plane');
if (issuer.endsWith('/')) throw projectError('control_plane must not have a trailing slash');

const hasApp = document.app !== undefined && document.app !== null;
const hasComponent = document.component !== undefined && document.component !== null;
if (hasApp === hasComponent) {
if (document.component !== undefined && document.component !== null) {
throw projectError(
`${PROJECT_FILE} must declare exactly one of app (a whole-house application) or component`,
'Use app: for a resident interface you draw yourself; component: for a semantic tree.',
`${PROJECT_FILE} declares component:, which Miakapp no longer displays`,
'Rename the section to app: and build a whole-house application (miakapp docs start).',
);
}
const section = hasApp ? 'app' : 'component';
const abi: ReleaseAbi = hasApp ? APP_ABI : COMPONENT_ABI;
if (document.app === undefined || document.app === null) {
throw projectError(`${PROJECT_FILE} must declare app: (a whole-house application)`);
}
const hasApp = true;
const section = 'app';
const abi: ReleaseAbi = APP_ABI;
const component = mapping(document[section], section);
exactKeys(component, ['artifact', 'release', 'requires'], section);
if (!isRelease(component.release)) {
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.15';
export const CLI_VERSION = '4.0.0';
21 changes: 3 additions & 18 deletions packages/cli/test/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ describe('offline commands', () => {
...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.json()['usage']).not.toContain('--kind');
expect(host.json()['usage']).toContain('default: dist/app.js)');
expect(host.stderr()).toBe('');
expect(secretReads).toBe(0);
expect(files.entries.size).toBe(0);
Expand Down Expand Up @@ -168,7 +168,7 @@ describe('offline commands', () => {

test('a missing artifact is an artifact failure', async () => {
const files = standardProject();
files.entries.delete(`${PROJECT_ROOT}/dist/component.js`);
files.entries.delete(`${PROJECT_ROOT}/dist/app.js`);
const host = testHost({ files });
expect(await run(['check'], host)).toBe(EXIT_CODE.artifact);
});
Expand Down Expand Up @@ -205,21 +205,6 @@ describe('offline commands', () => {
expect(JSON.parse(second.stdout()).abi).toBe('miakapp.app/1');
});

test('init --kind component keeps the semantic component shape', async () => {
const files = new MemoryFiles();
const host = testHost({ files });
expect(await run([
'init',
'--kind', 'component',
'--home', HOME_ID,
'--control-plane', 'https://control.example.test/api',
], host)).toBe(EXIT_CODE.success);
files.entries.set(`${PROJECT_ROOT}/dist/component.js`, new TextEncoder().encode(ARTIFACT_SOURCE));
const second = testHost({ files });
expect(await run(['check', '--json'], second)).toBe(EXIT_CODE.success);
expect(JSON.parse(second.stdout()).abi).toBe('miakapp.component/1');
});

test('init never overwrites an existing project file', async () => {
const host = testHost({ files: standardProject() });
const code = await run([
Expand Down
3 changes: 1 addition & 2 deletions packages/cli/test/starter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe('init --starter app', () => {
const { code, host } = await initStarter();
expect(code).toBe(EXIT_CODE.success);
expect(await listing(directory)).toEqual([...STARTER_FILES, 'miakapp.yaml'].sort());
expect(host.json()).toMatchObject({ kind: 'app', starter: 'app', artifact: 'dist/app.js', release: '0.1.0' });
expect(host.json()).toMatchObject({ starter: 'app', artifact: 'dist/app.js', release: '0.1.0' });

const manifest = await readFile(join(directory, 'miakapp.yaml'), 'utf8');
expect(manifest).toContain('home: demo-home\ncontrol_plane: https://control.example.test\n');
Expand Down Expand Up @@ -140,7 +140,6 @@ describe('init --starter app', () => {

for (const [label, argv] of [
['an unknown starter', ['--starter', 'component']],
['a component kind', ['--starter', 'app', '--kind', 'component']],
['an artifact needing shell quoting', ['--starter', 'app', '--artifact', 'dist/my app.js']],
['an artifact outside the project', ['--starter', 'app', '--artifact', '../app.js']],
['an artifact that is a starter file', ['--starter', 'app', '--artifact', 'app/main.ts']],
Expand Down
9 changes: 3 additions & 6 deletions packages/cli/test/support/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,14 @@ export const PROJECT_YAML = `schema: miakapp.project/1
home: test-home
control_plane: https://control.example.test/api

component:
artifact: dist/component.js
app:
artifact: dist/app.js
release: 2026-09-13.1
requires:
state_read:
- climate.living_room.temperature
event_subscribe: []
event_publish: []
call:
- lighting.set
presentation: []
`;

export class MemoryFiles implements FileSystem {
Expand Down Expand Up @@ -113,6 +110,6 @@ export function testHost(options: {
export function standardProject(artifact = ARTIFACT_SOURCE): MemoryFiles {
return new MemoryFiles({
[`${PROJECT_ROOT}/miakapp.yaml`]: PROJECT_YAML,
[`${PROJECT_ROOT}/dist/component.js`]: artifact,
[`${PROJECT_ROOT}/dist/app.js`]: artifact,
});
}
Loading