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
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
"name": "operations-pulse",
"source": "./plugins/claude/operations-pulse",
"strict": true,
"version": "0.1.0"
"version": "0.1.1"
}
],
"version": "0.1.0"
"version": "0.1.1"
}
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ load. Removal requires `schedule uninstall --confirm-uninstall`.
- `packages/core` — SQLite ledger and deterministic checks.
- `packages/mcp` — MCP stdio server.
- `skill/operations-pulse` — portable agent skill.
- `plugins/openai/operations-pulse` — generated Codex skill wrapper.
- `plugins/claude/operations-pulse` — generated Claude Code skill wrapper.
- `plugins/openai/operations-pulse` — generated Codex skill and MCP wrapper.
- `plugins/claude/operations-pulse` — generated Claude Code skill and MCP wrapper.
- `.agents/plugins/marketplace.json` — repository-local Codex marketplace.
- `.claude-plugin/marketplace.json` — repository-local Claude marketplace.
- `mcp-registry/operations-pulse/server.json` — staged official MCP Registry metadata.
Expand Down Expand Up @@ -153,11 +153,14 @@ npm run registration:sync
npm run registration:check
```

The wrappers intentionally register only the portable skill. They do not
install npm packages, register the MCP server, authenticate an account, or
change user-level host settings. The separately staged MCP identity is
`org.openlyuseful/operations-pulse`, backed by the future npm package
`@openly-useful/operations-pulse-mcp`.
The wrappers register the portable skill plus the immutable stdio command
`npx --yes @openly-useful/operations-pulse-mcp@0.1.0`. They do not authenticate
an account, install a scheduler, configure adapters, or collect credentials.
The corresponding MCP Registry identity is `org.openlyuseful/operations-pulse`.
After installing or upgrading the plugin, start a new Codex task or restart
Claude Code so the host refreshes its MCP tools. By default the MCP database is
`.operations-pulse/pulse.sqlite` under the host process working directory;
set `OPERATIONS_PULSE_DB` in the host environment to choose an explicit path.

Local generation and validation are allowed. Public repository creation,
package publication, MCP Registry submission, marketplace submission,
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "operations-pulse",
"version": "0.1.0",
"version": "0.1.1",
"private": true,
"description": "Local-first operational heartbeats, durable tickets, and governed tool connections.",
"license": "MIT",
Expand Down
3 changes: 2 additions & 1 deletion plugins/claude/operations-pulse/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
"description": "Run governed local operational heartbeats and maintain durable ticket memory.",
"homepage": "https://openlyuseful.org",
"license": "MIT",
"mcpServers": "./.mcp.json",
"name": "operations-pulse",
"repository": "https://github.com/Openly-Useful/operations-pulse",
"skills": "./skills/",
"version": "0.1.0"
"version": "0.1.1"
}
11 changes: 11 additions & 0 deletions plugins/claude/operations-pulse/.mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"mcpServers": {
"operations-pulse": {
"args": [
"--yes",
"@openly-useful/operations-pulse-mcp@0.1.0"
],
"command": "npx"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ read-only except for storing the pulse record.

## Run locally

These commands are for a source checkout after building the repository. A
marketplace plugin installs the portable skill only; it does not install an
npm package, register an MCP server, or change host settings.
These commands are for a source checkout after building the repository. The
marketplace plugin installs the portable skill and registers the pinned
`@openly-useful/operations-pulse-mcp@0.1.0` stdio server through `npx`. Start a
new task after install or upgrade so the host refreshes its MCP tool registry.
The plugin does not install a scheduler, configure adapters, or collect credentials.

```bash
node packages/core/dist/cli.js init
Expand Down
4 changes: 2 additions & 2 deletions plugins/openai/operations-pulse/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@
"longDescription": "Run local repository, documentation, and log checks; correlate signals with durable ticket memory; and explain governed connection options without treating agent output as authority.",
"privacyPolicyURL": "https://openlyuseful.org/legal/privacy",
"shortDescription": "Governed operational heartbeats and ticket memory",
"supportURL": "https://openlyuseful.org/support",
"termsOfServiceURL": "https://openlyuseful.org/legal/terms",
"websiteURL": "https://openlyuseful.org"
},
"license": "MIT",
"mcpServers": "./.mcp.json",
"name": "operations-pulse",
"repository": "https://github.com/Openly-Useful/operations-pulse",
"skills": "./skills/",
"version": "0.1.0"
"version": "0.1.1"
}
11 changes: 11 additions & 0 deletions plugins/openai/operations-pulse/.mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"mcpServers": {
"operations-pulse": {
"args": [
"--yes",
"@openly-useful/operations-pulse-mcp@0.1.0"
],
"command": "npx"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ read-only except for storing the pulse record.

## Run locally

These commands are for a source checkout after building the repository. A
marketplace plugin installs the portable skill only; it does not install an
npm package, register an MCP server, or change host settings.
These commands are for a source checkout after building the repository. The
marketplace plugin installs the portable skill and registers the pinned
`@openly-useful/operations-pulse-mcp@0.1.0` stdio server through `npx`. Start a
new task after install or upgrade so the host refreshes its MCP tool registry.
The plugin does not install a scheduler, configure adapters, or collect credentials.

```bash
node packages/core/dist/cli.js init
Expand Down
22 changes: 22 additions & 0 deletions scripts/sync-registrations.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
readdirSync,
readFileSync,
rmSync,
writeFileSync,
} from "node:fs";
import { dirname, join, relative, resolve } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
Expand All @@ -19,6 +20,17 @@ const wrapperSkillRoots = ["openai", "claude"].map((host) =>
);
const canonicalCatalogRoot = join(registrationRoot, "catalog");
const packagedCatalogRoot = join(registrationRoot, "packages", "mcp", "catalog");
const pluginMcpConfigPaths = ["openai", "claude"].map((host) =>
join(registrationRoot, "plugins", host, "operations-pulse", ".mcp.json"),
);
const pluginMcpConfig = Buffer.from(`${JSON.stringify({
mcpServers: {
"operations-pulse": {
args: ["--yes", "@openly-useful/operations-pulse-mcp@0.1.0"],
command: "npx",
},
},
}, null, 2)}\n`);

function walkFiles(directory) {
const files = [];
Expand Down Expand Up @@ -63,6 +75,12 @@ export function registrationSyncErrors() {
if (!expected.has(path)) errors.push(`Unexpected generated artifact file: ${relative(registrationRoot, join(target, path))}`);
}
}
for (const path of pluginMcpConfigPaths) {
if (!existsSync(path)) errors.push(`Generated artifact is missing ${relative(registrationRoot, path)}`);
else if (!readFileSync(path).equals(pluginMcpConfig)) {
errors.push(`Generated artifact drift: ${relative(registrationRoot, path)}`);
}
}
return errors;
}

Expand All @@ -85,6 +103,10 @@ export function writeRegistrationSkillCopies() {
count += 1;
}
}
for (const path of pluginMcpConfigPaths) {
writeFileSync(path, pluginMcpConfig);
count += 1;
}
return count;
}

Expand Down
25 changes: 20 additions & 5 deletions scripts/validate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const codexMarketplace = json(".agents/plugins/marketplace.json");
const claudeMarketplace = json(".claude-plugin/marketplace.json");
const codexPlugin = json("plugins/openai/operations-pulse/.codex-plugin/plugin.json");
const claudePlugin = json("plugins/claude/operations-pulse/.claude-plugin/plugin.json");
const codexMcp = json("plugins/openai/operations-pulse/.mcp.json");
const claudeMcp = json("plugins/claude/operations-pulse/.mcp.json");

const expectedPublisher = {
name: "Openly Useful",
Expand All @@ -47,6 +49,14 @@ const expectedPolicies = {
const expectedRepository = "https://github.com/Openly-Useful/operations-pulse";
const expectedMcpName = "org.openlyuseful/operations-pulse";
const expectedMcpPackage = "@openly-useful/operations-pulse-mcp";
const expectedPluginMcp = {
mcpServers: {
"operations-pulse": {
args: ["--yes", "@openly-useful/operations-pulse-mcp@0.1.0"],
command: "npx",
},
},
};

function same(left, right) {
return JSON.stringify(left) === JSON.stringify(right);
Expand Down Expand Up @@ -183,8 +193,8 @@ if (publisher.lastUpdated !== "2026-08-23") {
fail("Publisher mirror projection date is not current.");
}

if (rootPackage.version !== corePackage.version || rootPackage.version !== mcpPackage.version) {
fail("Root, core, and MCP package versions must agree.");
if (rootPackage.version !== "0.1.1" || corePackage.version !== "0.1.0" || mcpPackage.version !== "0.1.0") {
fail("Plugin release must be 0.1.1 while immutable npm artifacts remain 0.1.0.");
}
if (mcpPackage.name !== expectedMcpPackage || mcpPackage.mcpName !== expectedMcpName) {
fail("MCP npm package name or official MCP Registry identity is invalid.");
Expand Down Expand Up @@ -240,6 +250,7 @@ if (claudeMarketplace.$schema !== "https://json.schemastore.org/claude-code-mark
|| claudeMarketplace.version !== rootPackage.version
|| claudeMarketplace.plugins?.length !== 1
|| claudeMarketplace.plugins[0]?.source !== "./plugins/claude/operations-pulse"
|| claudeMarketplace.plugins[0]?.version !== rootPackage.version
|| claudeMarketplace.plugins[0]?.strict !== true) {
fail("Claude marketplace metadata is invalid.");
}
Expand All @@ -253,21 +264,23 @@ for (const [label, manifest] of [["Codex", codexPlugin], ["Claude", claudePlugin
|| manifest.homepage !== expectedPublisher.url
|| manifest.repository !== expectedRepository
|| manifest.skills !== "./skills/"
|| "mcpServers" in manifest) {
fail(`${label} plugin manifest identity or skill-only boundary is invalid.`);
|| manifest.mcpServers !== "./.mcp.json") {
fail(`${label} plugin manifest identity or MCP registration boundary is invalid.`);
}
validateAuthor(manifest.author, `${label} plugin author`);
}
if (codexPlugin.interface?.developerName !== expectedPublisher.name
|| codexPlugin.interface?.privacyPolicyURL !== expectedPolicies.privacy
|| codexPlugin.interface?.termsOfServiceURL !== expectedPolicies.terms
|| codexPlugin.interface?.supportURL !== expectedPolicies.support
|| codexPlugin.interface?.websiteURL !== expectedPublisher.url) {
fail("Codex public interface URLs must derive from the publisher record.");
}
if (claudePlugin.$schema !== "https://json.schemastore.org/claude-code-plugin-manifest.json") {
fail("Claude plugin manifest schema is invalid.");
}
if (!same(codexMcp, expectedPluginMcp) || !same(claudeMcp, expectedPluginMcp)) {
fail("Codex and Claude plugins must pin the immutable Operations Pulse MCP stdio package.");
}

for (const error of registrationSyncErrors()) fail(error);

Expand All @@ -281,6 +294,8 @@ for (const required of [
"publisher/publisher.json",
".agents/plugins/marketplace.json",
".claude-plugin/marketplace.json",
"plugins/openai/operations-pulse/.mcp.json",
"plugins/claude/operations-pulse/.mcp.json",
"mcp-registry/operations-pulse/server.json",
"packages/mcp/README.md",
"packages/mcp/LICENSE",
Expand Down
8 changes: 5 additions & 3 deletions skill/operations-pulse/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ read-only except for storing the pulse record.

## Run locally

These commands are for a source checkout after building the repository. A
marketplace plugin installs the portable skill only; it does not install an
npm package, register an MCP server, or change host settings.
These commands are for a source checkout after building the repository. The
marketplace plugin installs the portable skill and registers the pinned
`@openly-useful/operations-pulse-mcp@0.1.0` stdio server through `npx`. Start a
new task after install or upgrade so the host refreshes its MCP tool registry.
The plugin does not install a scheduler, configure adapters, or collect credentials.

```bash
node packages/core/dist/cli.js init
Expand Down
33 changes: 30 additions & 3 deletions tests/registration.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ test("publisher mirror keeps the one-entity boundary founder-operated and format
]);
});

test("Codex and Claude catalogs point to self-contained generated skill wrappers", () => {
test("Codex and Claude catalogs point to generated plugin wrappers", () => {
const codex = json(".agents/plugins/marketplace.json");
const claude = json(".claude-plugin/marketplace.json");
const version = json("package.json").version;
Expand Down Expand Up @@ -73,11 +73,10 @@ test("provider wrapper manifests derive publisher identity and policy URLs", ()
assert.equal(manifest.version, version);
assert.equal(manifest.skills, "./skills/");
assert.deepEqual(manifest.author, author);
assert.equal("mcpServers" in manifest, false);
assert.equal(manifest.mcpServers, "./.mcp.json");
}
assert.equal(openai.interface.privacyPolicyURL, publisher.policies.privacy);
assert.equal(openai.interface.termsOfServiceURL, publisher.policies.terms);
assert.equal(openai.interface.supportURL, publisher.policies.support);
});

test("official MCP Registry and npm identities agree", () => {
Expand All @@ -96,6 +95,34 @@ test("official MCP Registry and npm identities agree", () => {
assert.equal(registry.packages[0].transport.type, "stdio");
});

test("Codex and Claude plugins register the immutable MCP stdio package", () => {
const pluginVersion = json("package.json").version;
const mcpPackage = json("packages/mcp/package.json");
const codexPlugin = json("plugins/openai/operations-pulse/.codex-plugin/plugin.json");
const claudePlugin = json("plugins/claude/operations-pulse/.claude-plugin/plugin.json");
const claudeMarketplace = json(".claude-plugin/marketplace.json");
const expectedMcp = {
mcpServers: {
"operations-pulse": {
args: ["--yes", "@openly-useful/operations-pulse-mcp@0.1.0"],
command: "npx",
},
},
};

assert.equal(pluginVersion, "0.1.1");
assert.equal(mcpPackage.version, "0.1.0");
assert.equal(codexPlugin.version, pluginVersion);
assert.equal(claudePlugin.version, pluginVersion);
assert.equal(claudeMarketplace.version, pluginVersion);
assert.equal(claudeMarketplace.plugins[0].version, pluginVersion);
assert.equal(codexPlugin.mcpServers, "./.mcp.json");
assert.equal(claudePlugin.mcpServers, "./.mcp.json");
assert.deepEqual(json("plugins/openai/operations-pulse/.mcp.json"), expectedMcp);
assert.deepEqual(json("plugins/claude/operations-pulse/.mcp.json"), expectedMcp);
assert.equal("env" in expectedMcp.mcpServers["operations-pulse"], false);
});

test("npm artifact gate accepts founder authorization while provider review remains tracked", () => {
const publisher = json("publisher/publisher.json");
assert.deepEqual(publicationErrors(publisher), []);
Expand Down