diff --git a/packages/cli/LICENSE b/packages/cli/LICENSE new file mode 100644 index 0000000..c5247b7 --- /dev/null +++ b/packages/cli/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2026 Mathieu Colmon + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/packages/cli/package.json b/packages/cli/package.json index 1067494..0e5c0a1 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -3,7 +3,6 @@ "version": "4.0.0-alpha.0", "description": "Agent-first Miakapp command line: check, build, publish, activate and roll back home components", "type": "module", - "private": true, "engines": { "bun": ">=1.2.23", "node": ">=22.9" @@ -31,8 +30,7 @@ "directory": "packages/cli" }, "publishConfig": { - "access": "public", - "tag": "next" + "access": "public" }, "keywords": [ "Miakapp", diff --git a/packages/cli/src/agent-pack.ts b/packages/cli/src/agent-pack.ts index bebb578..4663196 100644 --- a/packages/cli/src/agent-pack.ts +++ b/packages/cli/src/agent-pack.ts @@ -18,6 +18,7 @@ * Bytes outside those regions are copied through untouched. */ import { projectError } from './errors.js'; +import { CLI_VERSION, PACKAGE_NAME } from './version.js'; /** Directory the pack owns inside the owner's repository. */ export const PACK_DIRECTORY = '.miakapp'; @@ -163,15 +164,23 @@ export function mergeInstructions(existing: string | undefined, block: string): /** * The server entry, as a client expects to find it. * - * `command` is the bare binary name: the pack is written into a repository that - * may be opened on another machine, where an absolute path from this one would - * resolve to nothing. + * An absolute path is wrong: the pack is written into a repository that may be + * opened on another machine, where a path from this one resolves to nothing. + * The bare binary name is wrong for the same reason in reverse — it assumes a + * global install nobody performed, so the server simply fails to start in the + * repository the pack was meant to equip. Both were observed: a rehearsal in a + * fresh repository found `command: "miakapp"` unresolvable. + * + * `npx` resolves the published package on any machine with Node, installing it + * on first use. The version is pinned rather than floating because the guide + * written beside this file is the guide of *this* release: a floating spec + * would pair one release's prose with another release's tool surface. */ export function serverEntry(): Record { return { type: 'stdio', - command: SERVER_NAME, - args: ['mcp'], + command: 'npx', + args: ['-y', `${PACKAGE_NAME}@${CLI_VERSION}`, 'mcp'], }; } diff --git a/packages/cli/src/main.ts b/packages/cli/src/main.ts index 758b58d..c3babd5 100644 --- a/packages/cli/src/main.ts +++ b/packages/cli/src/main.ts @@ -15,6 +15,7 @@ * - `--json`, which prints exactly one closed object on stdout. */ import { installPack } from './agent-pack.js'; +import { CLI_VERSION } from './version.js'; import { prepareArtifact, type Artifact } from './artifact.js'; import { exchangePublisherToken, fetchDiscovery } from './control-plane.js'; import { discoverFlows, inventoryJson, type Inventory } from './discovery.js'; @@ -38,7 +39,7 @@ import { type PublicationTarget, } from './publication.js'; -export const CLI_VERSION = '4.0.0-alpha.0'; +export { CLI_VERSION }; /** * The Home Key is read from the environment only. A secret passed as an diff --git a/packages/cli/src/version.ts b/packages/cli/src/version.ts new file mode 100644 index 0000000..f6d7b13 --- /dev/null +++ b/packages/cli/src/version.ts @@ -0,0 +1,15 @@ +/** + * The package's own identity, in one place. + * + * `main.ts` imports `agent-pack.ts`, so the constants cannot live in `main.ts` + * without a cycle: the pack needs them to write a runnable MCP entry. + * + * Both values are duplicates of `package.json`, which cannot be imported here + * without a JSON import assertion the build does not emit. `test/version.test.ts` + * compares them to the manifest, because the drift is not cosmetic: the pack + * writes `PACKAGE_NAME@CLI_VERSION` into a repository, and a stale version + * would resolve a different release than the guide shipped beside it. + */ +export const PACKAGE_NAME = '@miakapp/cli'; + +export const CLI_VERSION = '4.0.0-alpha.0'; diff --git a/packages/cli/test/agent-pack.test.ts b/packages/cli/test/agent-pack.test.ts index c1d98ea..82ed261 100644 --- a/packages/cli/test/agent-pack.test.ts +++ b/packages/cli/test/agent-pack.test.ts @@ -16,6 +16,7 @@ import { import { EXIT_CODE } from '../src/errors.js'; import { guideAssetPath, run } from '../src/main.js'; import { TOOLS, buildArgv } from '../src/mcp.js'; +import { CLI_VERSION, PACKAGE_NAME } from '../src/version.js'; import { MemoryFiles, PROJECT_ROOT, testHost } from './support/host.js'; const GUIDE_TEXT = '# Building a Miakapp home\n\nThe packaged guide.\n'; @@ -57,11 +58,21 @@ describe('the pack installs into an empty repository', () => { expect(config(files)['mcpServers'][SERVER_NAME]).toEqual(serverEntry()); }); - test('the server is launched by bare name, so the repository is portable', () => { + // This assertion used to require the bare binary name, on the reasoning that + // a name travels between machines and a path does not. The reasoning held; + // the conclusion did not. Rehearsed in a fresh repository, `command: + // "miakapp"` names a binary nobody installed, so the server never starts. + // Portability is still the property under test — it now has to be satisfied + // by something the repository can actually run. + test('the server is launched by something a fresh repository can run', () => { const entry = serverEntry(); - expect(entry['command']).toBe(SERVER_NAME); - expect(entry['args']).toEqual(['mcp']); - expect(JSON.stringify(entry)).not.toContain('/'); + expect(entry['command']).toBe('npx'); + expect(entry['args']).toEqual(['-y', `${PACKAGE_NAME}@${CLI_VERSION}`, 'mcp']); + expect(entry['args']).not.toContain(PROJECT_ROOT); + }); + + test('the entry is keyed by the server name, whatever launches it', () => { + expect(Object.keys({ [SERVER_NAME]: serverEntry() })).toEqual([SERVER_NAME]); }); test('it installs where --dir points, not where the process happens to be', async () => { diff --git a/packages/cli/test/version.test.ts b/packages/cli/test/version.test.ts new file mode 100644 index 0000000..ea805bf --- /dev/null +++ b/packages/cli/test/version.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, test } from 'bun:test'; + +import { CLI_VERSION, PACKAGE_NAME } from '../src/version.js'; +import { serverEntry } from '../src/agent-pack.js'; + +const manifest = (await Bun.file(new URL('../package.json', import.meta.url)).json()) as { + name: string; + version: string; +}; + +describe('package identity', () => { + // These two constants are a hand-kept duplicate of the manifest. That is + // tolerable only while something compares them: `agent-pack` writes + // `name@version` into someone else's repository, so a stale constant + // silently pins a release that is not the one shipping the guide next to it. + test('CLI_VERSION matches the published manifest version', () => { + expect(CLI_VERSION).toBe(manifest.version); + }); + + test('PACKAGE_NAME matches the published manifest name', () => { + expect(PACKAGE_NAME).toBe(manifest.name); + }); +}); + +describe('the MCP entry the pack installs', () => { + // A fresh owner repository has no globally installed `miakapp`; the entry has + // to bring its own tool. This asserts the property, not the spelling. + test('names no bare binary that a fresh repository would lack', () => { + const entry = serverEntry(); + expect(entry['command']).not.toBe('miakapp'); + }); + + test('resolves the exact published release through npx', () => { + expect(serverEntry()).toEqual({ + type: 'stdio', + command: 'npx', + args: ['-y', `${manifest.name}@${manifest.version}`, 'mcp'], + }); + }); + + test('carries no absolute path from the machine that wrote it', () => { + const entry = serverEntry(); + const words = [entry['command'] as string, ...(entry['args'] as string[])]; + for (const word of words) { + expect(word.startsWith('/')).toBe(false); + } + }); +}); diff --git a/packages/component/LICENSE b/packages/component/LICENSE new file mode 100644 index 0000000..c5247b7 --- /dev/null +++ b/packages/component/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2026 Mathieu Colmon + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/packages/component/package.json b/packages/component/package.json index d5caa43..346162a 100644 --- a/packages/component/package.json +++ b/packages/component/package.json @@ -3,7 +3,6 @@ "version": "4.0.0-alpha.0", "description": "Guest SDK for Miakapp home components: semantic UI, granted state, calls and events inside the sandboxed Worker", "type": "module", - "private": true, "engines": { "bun": ">=1.2.23", "node": ">=22.9" @@ -32,8 +31,7 @@ "directory": "packages/component" }, "publishConfig": { - "access": "public", - "tag": "next" + "access": "public" }, "keywords": [ "Miakapp",