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 .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 12 additions & 12 deletions docs/IMPLEMENTATION_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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-<version>.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.

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions scripts/smoke-install-paths.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ try {
"npm",
[
"install",
"--omit=dev",
"--ignore-scripts",
"--no-package-lock",
"--legacy-peer-deps",
Expand Down
11 changes: 11 additions & 0 deletions src/__tests__/documentation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
15 changes: 13 additions & 2 deletions src/__tests__/metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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");
Expand All @@ -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");
Expand Down
Loading