diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 70f1541..f314af8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: with: node-version: 22.22.3 cache: npm - - run: npm install + - run: npm ci - run: npm test - run: npm run build - run: npm run package:validate diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 599ab77..96f6935 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,7 @@ jobs: echo "::error::Tag ${GITHUB_REF_NAME} does not match package version ${package_version}." exit 1 fi - - run: npm install + - run: npm ci - run: npm test - run: npm run build - run: npm run package:validate diff --git a/README.md b/README.md index 30f5fde..e2aa86b 100644 --- a/README.md +++ b/README.md @@ -7,16 +7,20 @@ Build the JavaScript artifact before installing it: ```bash -npm install +npm ci npm test npm run build npm run package:validate ``` +The build checkout intentionally installs development dependencies, including the +pinned OpenClaw `2026.6.1` SDK compatibility baseline. Deployments do not need +that development tree. + From an npm-managed local OpenClaw `2026.7.2-beta.6` or newer checkout, install the built working directory: ```bash -npm install /absolute/path/to/openclaw-armada-dm +npm install --omit=dev /absolute/path/to/openclaw-armada-dm openclaw config set plugins.load.paths \ '["/absolute/path/to/openclaw-checkout/node_modules/openclaw-armada-dm"]' \ --strict-json diff --git a/docs/IMPLEMENTATION_PLAN.md b/docs/IMPLEMENTATION_PLAN.md index 71a58a4..84d972c 100644 --- a/docs/IMPLEMENTATION_PLAN.md +++ b/docs/IMPLEMENTATION_PLAN.md @@ -184,13 +184,13 @@ Crypto and relay code must not import OpenClaw agent/runtime internals. OpenClaw Use the pinned Ink-North repository as the OpenClaw integration template, with the OpenClaw `2026.6.1` source and documentation taking precedence over legacy template fields: -| Template area | Action in this repository | -| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `package.json` OpenClaw metadata | Reproduce the extension/setup/channel/install/compatibility metadata under the single consistent package name `openclaw-armada-dm`; add current built-runtime entry fields and a strict `files` allowlist. | -| Runtime/setup entry separation | Keep `index.ts` and the minimal compatibility-only `setup-entry.ts` import-safe and publish matching built entries. Importing either entry must not prompt, mutate configuration, read secrets, or load transport. | -| Channel registration/runtime injection | Adapt the public SDK registration and runtime setter for channel ID `nostr`, while keeping plugin ID `armada-dm`. | -| Single-owner DM policy integration | Use OpenClaw's ingress authorization with `useDefaultPairingStore: false`; require one configured owner and do not create or consume pairing state. | -| Local npm workflow | Preserve the ability to install the built package with `npm install /absolute/path/to/openclaw-armada-dm` from an npm-managed local OpenClaw checkout, and also test the supported managed `npm-pack:` flow. | +| Template area | Action in this repository | +| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `package.json` OpenClaw metadata | Reproduce the extension/setup/channel/install/compatibility metadata under the single consistent package name `openclaw-armada-dm`; add current built-runtime entry fields and a strict `files` allowlist. | +| Runtime/setup entry separation | Keep `index.ts` and the minimal compatibility-only `setup-entry.ts` import-safe and publish matching built entries. Importing either entry must not prompt, mutate configuration, read secrets, or load transport. | +| Channel registration/runtime injection | Adapt the public SDK registration and runtime setter for channel ID `nostr`, while keeping plugin ID `armada-dm`. | +| Single-owner DM policy integration | Use OpenClaw's ingress authorization with `useDefaultPairingStore: false`; require one configured owner and do not create or consume pairing state. | +| Local npm workflow | Preserve the ability to install the built package with `npm install --omit=dev /absolute/path/to/openclaw-armada-dm` from an npm-managed local OpenClaw checkout, and also test the supported managed `npm-pack:` flow. | Do not copy the template's NIP-04 path, multi-account behavior, permissive schemas, outer-event-ID dedupe, configured-relay-only publication, sender/event logging, legacy `kind: "bundled-channel-entry"`, or `openclaw.bundle.json`. This project is a native, single-account, NIP-17-only plugin. @@ -245,10 +245,10 @@ Published or packed artifacts must already contain built JavaScript. Installatio Two clean-host paths must be tested: -1. Literal local-checkout workflow requested for this project: from an npm-managed local OpenClaw checkout, run `npm install /absolute/path/to/openclaw-armada-dm`, enable `armada-dm`, restart the gateway, and verify runtime discovery. +1. Literal local-checkout workflow requested for this project: from an npm-managed local OpenClaw checkout, run `npm install --omit=dev /absolute/path/to/openclaw-armada-dm`, enable `armada-dm`, restart the gateway, and verify runtime discovery. 2. OpenClaw-managed local artifact workflow: run `npm pack`, then `openclaw plugins install npm-pack:/absolute/path/openclaw-armada-dm-.tgz`, enable the plugin, and verify runtime discovery. -The README must explain that a bare `npm install` is only the contract for an npm-managed local OpenClaw checkout. Other installations should use OpenClaw's managed `npm-pack:` command so the plugin is registered, dependency-checked, and removable through OpenClaw. +The README must explain that `npm install --omit=dev` is only the contract for an npm-managed local OpenClaw checkout. Other installations should use OpenClaw's managed `npm-pack:` command so the plugin is registered, dependency-checked, and removable through OpenClaw. Because `nostr` is a shared channel ID, installation must fail clearly if another enabled plugin already owns it. The migration guide must tell operators to disable/remove the previous `nostr-nip17` or bundled Nostr channel plugin while preserving `channels.nostr` and `secrets.providers.nostr`, then enable plugin ID `armada-dm`. Existing pairing approvals may remain on disk but are deliberately ignored by this plugin; installation must not delete them or broaden the configured owner. Contract tests must prove that ownership conflicts do not silently select one implementation. @@ -595,7 +595,7 @@ Slices are sequential: an agent starts only after every dependency's exit gate i - `npm test`, `npm run lint`, `npm run build`, package validation, and `npm pack --dry-run` pass. - `npm run audit` reports zero production and deploy-host vulnerabilities, checks the complete tree, and rejects every finding outside the explicitly isolated development-only `openclaw@2026.6.1` SDK baseline. -- A clean npm-managed local patched OpenClaw host discovers the plugin after `npm install /absolute/path/to/openclaw-armada-dm`; a clean managed patched host also discovers the packed tarball through `npm-pack:`. +- A clean npm-managed local patched OpenClaw host discovers the plugin after `npm install --omit=dev /absolute/path/to/openclaw-armada-dm`; a clean managed patched host also discovers the packed tarball through `npm-pack:`. - The migration smoke test disables the prior Nostr plugin, preserves `secrets.providers.nostr` and `channels.nostr`, configures exactly one owner, enables plugin `armada-dm`, audits/reloads secrets, restarts the gateway, and proves existing `nostr` pairing approvals are ignored rather than deleted or imported. ### Coverage group 2 — NIP-17 cryptographic core @@ -737,7 +737,7 @@ Slices are sequential: an agent starts only after every dependency's exit gate i **Release** -- Pack an initial prerelease and test both local `npm install` and OpenClaw-managed `npm-pack:` installation against the exact `2026.6.1` API baseline and a deployable OpenClaw `2026.7.2-beta.6` or newer host. Registry publication is optional. +- Pack an initial prerelease and test both local `npm install --omit=dev` and OpenClaw-managed `npm-pack:` installation against the exact `2026.6.1` API baseline and a deployable OpenClaw `2026.7.2-beta.6` or newer host. Registry publication is optional. - Promote to `1.0.0` only after the current Armada client initiates a conversation, receives an automatic reply, and the bot safely recognizes its recovery copy. ## 15. Required documentation @@ -761,7 +761,7 @@ The README should include: The first release is complete when all of the following are true: -- An npm-managed local OpenClaw checkout can discover the plugin after local `npm install`, and a clean standard OpenClaw installation can install the packed package through `npm-pack:`; both can configure, enable, disable, inspect, and remove it. +- An npm-managed local OpenClaw checkout can discover the plugin after local `npm install --omit=dev`, and a clean standard OpenClaw installation can install the packed package through `npm-pack:`; both can configure, enable, disable, inspect, and remove it. - All required manual configuration, secret, channel, single-owner ingress, reply-delivery, status, and lifecycle flows use only the OpenClaw `2026.6.1` (`2e08f0f`) public SDK surface, and the package advertises patched OpenClaw `2026.7.2-beta.6` as its minimum deployable host. - A pre-existing owner of channel `nostr` produces a clear conflict; migration to plugin `armada-dm` preserves the operator's `channels.nostr` and `secrets.providers.nostr` configuration only after the previous owner is disabled or removed. - The plugin exposes direct text chat and encrypted inbound-media capability only. diff --git a/docs/implementation-slices/01-installable-plugin-and-identity.md b/docs/implementation-slices/01-installable-plugin-and-identity.md index 10859d4..f66452e 100644 --- a/docs/implementation-slices/01-installable-plugin-and-identity.md +++ b/docs/implementation-slices/01-installable-plugin-and-identity.md @@ -63,7 +63,7 @@ This is an operator-facing slice. It does not yet exchange Nostr messages. ## Exit gate - Tests, lint, typecheck/build, package validation, `npm pack --dry-run`, and the complete-tree/zero-runtime-vulnerability audit pass. -- A clean npm-managed OpenClaw `2026.7.2-beta.6` or newer checkout discovers the plugin after `npm install /absolute/path/to/openclaw-armada-dm`. +- A clean npm-managed OpenClaw `2026.7.2-beta.6` or newer checkout discovers the plugin after `npm install --omit=dev /absolute/path/to/openclaw-armada-dm`. - A clean OpenClaw `2026.7.2-beta.6` or newer instance discovers the packed artifact through `openclaw plugins install npm-pack:/absolute/path/to/package.tgz`. - With the documented pre-provisioned SecretRef, status shows the correct bot `npub` and never the secret. - No relay connection or message processing exists yet. diff --git a/docs/implementation-slices/08-setup-interoperability-and-release.md b/docs/implementation-slices/08-setup-interoperability-and-release.md index 000c03d..5a4745d 100644 --- a/docs/implementation-slices/08-setup-interoperability-and-release.md +++ b/docs/implementation-slices/08-setup-interoperability-and-release.md @@ -74,7 +74,7 @@ An operator who provisioned `/path/to/.openclaw/secrets/nostr_nsec`, installed t ## Exit gate -- Both local `npm install /absolute/path/to/openclaw-armada-dm` and managed `npm-pack:` workflows are documented and validated against OpenClaw `2026.7.2-beta.6` or newer. +- Both local `npm install --omit=dev /absolute/path/to/openclaw-armada-dm` and managed `npm-pack:` workflows are documented and validated against OpenClaw `2026.7.2-beta.6` or newer. - Adding the documented configuration and restarting the gateway is sufficient to publish/verify the bot inbox and reach ready status; no setup command is required. - The current Armada client sends a NIP-17 DM that reaches the intended OpenClaw agent once under normal duplicate delivery, then decrypts and renders the bot's response. - Human-initiated multi-turn replies, sender recovery copies, sole-owner allowlist, restart recovery, partial relay failure, authoritative kind-10050 routing, and automatic owner fallback have automated or manual evidence. diff --git a/scripts/smoke-install-paths.mjs b/scripts/smoke-install-paths.mjs index 69071be..212e3d3 100644 --- a/scripts/smoke-install-paths.mjs +++ b/scripts/smoke-install-paths.mjs @@ -37,6 +37,7 @@ try { "npm", [ "install", + "--omit=dev", "--ignore-scripts", "--no-package-lock", "--legacy-peer-deps", diff --git a/src/__tests__/documentation.test.ts b/src/__tests__/documentation.test.ts index 2bb6dae..77acad9 100644 --- a/src/__tests__/documentation.test.ts +++ b/src/__tests__/documentation.test.ts @@ -4,6 +4,17 @@ import { describe, expect, it } from "vitest"; const root = new URL("../../", import.meta.url); describe("manual configuration documentation", () => { + it("omits development dependencies from local plugin installation", async () => { + const readme = await readFile(new URL("README.md", root), "utf8"); + + expect(readme).toContain( + "npm install --omit=dev /absolute/path/to/openclaw-armada-dm", + ); + expect(readme).not.toContain( + "npm install /absolute/path/to/openclaw-armada-dm", + ); + }); + it("documents openclaw.json configuration without requiring a wizard", async () => { const readme = await readFile(new URL("README.md", root), "utf8"); const slice = await readFile( diff --git a/src/__tests__/metadata.test.ts b/src/__tests__/metadata.test.ts index 7c19d64..a3c69da 100644 --- a/src/__tests__/metadata.test.ts +++ b/src/__tests__/metadata.test.ts @@ -112,6 +112,15 @@ describe("package and manifest contracts", () => { expect(workflow).toContain("npm run ci"); }); + it("omits development dependencies in the local-install smoke test", async () => { + const smoke = await readFile( + new URL("scripts/smoke-install-paths.mjs", root), + "utf8", + ); + + expect(smoke).toContain('"--omit=dev"'); + }); + it("packs a checksummed artifact after changes reach main", async () => { const workflow = await readFile( new URL(".github/workflows/ci.yml", root), @@ -120,7 +129,8 @@ describe("package and manifest contracts", () => { expect(workflow).toContain("github.event_name == 'push'"); expect(workflow).toContain("github.ref == 'refs/heads/main'"); - expect(workflow).toContain("npm install"); + expect(workflow).toContain("npm ci"); + expect(workflow).not.toContain("npm install"); expect(workflow).toContain("npm test"); expect(workflow).toContain("npm run build"); expect(workflow).toContain("npm run package:validate"); @@ -138,7 +148,8 @@ describe("package and manifest contracts", () => { expect(workflow).toContain('tags: ["v*"]'); expect(workflow).toContain("contents: write"); - expect(workflow).toContain("npm install"); + expect(workflow).toContain("npm ci"); + expect(workflow).not.toContain("npm install"); expect(workflow).toContain("npm test"); expect(workflow).toContain("npm run build"); expect(workflow).toContain("npm run package:validate");