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
106 changes: 106 additions & 0 deletions .github/workflows/release-cli.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: Release CLI

on:
pull_request:
branches:
- master
types:
- closed
paths:
- "packages/cli/**"
- ".github/workflows/release-cli.yml"
push:
branches:
- master
paths:
- "packages/cli/**"
Comment on lines +12 to +16

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Trigger the release workflow only once per merged PR

When a PR that changes packages/cli/** is merged into master, GitHub emits both the pull_request: closed event configured above and this push event. GitHub documents that a merged PR's GITHUB_REF is the target branch ref, so both runs also share the concurrency key from lines 21-23 (GitHub Docs). Depending on event ordering, the PR run can cancel a push run while it is publishing, or the push run can execute afterward and attempt to publish the same immutable npm and ClawHub versions again. Keep only one publish trigger, or make one event validation-only.

Useful? React with 👍 / 👎.


permissions:
contents: read

concurrency:
group: release-cli-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
validate:
name: Validate CLI release
if: github.event_name == 'push' || github.event.pull_request.merged == true
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup pnpm
uses: pnpm/action-setup@v6

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Test CLI
run: pnpm --filter @clankeroverflow/cli test

- name: Check CLI types
run: pnpm --filter @clankeroverflow/cli check-types

- name: Build CLI and plugin manifests
run: pnpm --filter @clankeroverflow/cli build

- name: Check package contents
run: pnpm --filter @clankeroverflow/cli pack --pack-destination /tmp

- name: Preview ClawHub bundle
run: pnpm dlx clawhub@0.18.0 package publish ./packages/cli --family bundle-plugin --dry-run

publish:
name: Publish CLI and plugins
if: github.event_name == 'push' || github.event.pull_request.merged == true
needs: validate
runs-on: ubuntu-latest
timeout-minutes: 15
environment: production
permissions:
contents: read
id-token: write
env:
CLAWHUB_TOKEN: ${{ secrets.CLAWHUB_TOKEN }}

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup pnpm
uses: pnpm/action-setup@v6

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
registry-url: https://registry.npmjs.org

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Publish CLI package
working-directory: packages/cli
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: pnpm publish --access public --provenance --no-git-checks

- name: Configure ClawHub token fallback
if: env.CLAWHUB_TOKEN != ''
run: pnpm dlx clawhub@0.18.0 login --token "$CLAWHUB_TOKEN" --no-browser
Comment on lines +101 to +103

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require a ClawHub token for non-dispatch releases

When CLAWHUB_TOKEN is intentionally omitted as allowed by the new README's OIDC guidance, this conditional skips the only login step and the subsequent ClawHub publish runs unauthenticated. The official ClawHub workflow documentation explicitly states that tokenless trusted publishing works only for workflow_dispatch; other trusted release events require clawhub_token (ClawHub CLI docs). This workflow has only push and pull_request: closed triggers, so id-token: write is not a usable fallback here. Require CLAWHUB_TOKEN, or move tokenless publishing to a supported dispatch flow.

Useful? React with 👍 / 👎.


- name: Publish ClawHub bundle
run: pnpm dlx clawhub@0.18.0 package publish ./packages/cli --family bundle-plugin --owner clankeroverflow
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Supported agents:

- Codex
- Claude Code
- OpenClaw
- OpenCode
- Pi
- Cursor
Expand Down Expand Up @@ -110,6 +111,24 @@ To configure an MCP client manually, run the published package over stdio:

`CLANKER_API_KEY` is optional for search-only access.

## OpenClaw

ClankerOverflow ships an OpenClaw-compatible bundle for ClawHub. After the package is published, install it with:

```bash
openclaw plugins install clawhub:@clankeroverflow/cli
```

The bundle exposes the ClankerOverflow skills and MCP server to OpenClaw. To preview a ClawHub release from this repository without uploading it:

```bash
clawhub package publish ./packages/cli --family bundle-plugin --dry-run
```

CLI and plugin releases are automated by `.github/workflows/release-cli.yml`. When a pull request into `master` modifies `packages/cli` and is merged, or when a matching commit is pushed directly to `master`, the workflow validates the npm package, previews the ClawHub bundle, publishes the npm package, and publishes the ClawHub bundle. Pull request updates do not trigger this release workflow.

Configure the `NPM_TOKEN` repository secret for npm publishing. ClawHub publishing uses GitHub Actions OIDC when trusted publishing is configured; add a `CLAWHUB_TOKEN` repository secret as a fallback. Bump `packages/cli/package.json` before merging a release.

## Local SQLite Mode

Use the MCP server without the hosted service:
Expand Down
15 changes: 15 additions & 0 deletions packages/cli/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "clankeroverflow",
"version": "1.0.12",
"description": "Search-first memory for AI coding agents — log and reuse verified fixes across sessions",
"author": {
"name": "ClankerOverflow",
"url": "https://clankeroverflow.com"
},
"homepage": "https://clankeroverflow.com",
"repository": "https://github.com/oussama/clankeroverflow",
"license": "MIT",
"keywords": ["debugging", "solutions", "knowledge-base", "mcp"],
"skills": "./skills/",
"mcpServers": "./.mcp.json"
}
11 changes: 11 additions & 0 deletions packages/cli/openclaw.plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": "clankeroverflow",
"name": "ClankerOverflow",
"description": "Search-first memory for AI coding agents — log and reuse verified fixes across sessions",
"version": "1.0.12",
"configSchema": {
"type": "object",
"additionalProperties": false
},
"skills": ["./skills"]
}
2 changes: 2 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
},
"files": [
".claude-plugin",
".codex-plugin",
".mcp.json",
"commands",
"dist",
"hooks",
"openclaw.plugin.json",
"skills"
],
"type": "module",
Expand Down
18 changes: 18 additions & 0 deletions packages/cli/src/package.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { describe, expect, test } from "vitest";
import packageJson from "../package.json";
import codexPluginJson from "../.codex-plugin/plugin.json";
import openClawPluginJson from "../openclaw.plugin.json";

describe("packages/cli package metadata", () => {
test("publishes bundled skills without a package install hook", () => {
expect(packageJson.files).toContain("dist");
expect(packageJson.files).toContain("skills");
expect(packageJson.files).toContain(".codex-plugin");
expect(packageJson.files).toContain("openclaw.plugin.json");
expect(packageJson.files).not.toContain("postinstall.mjs");
expect(
(packageJson.scripts as Record<string, string> | undefined)?.postinstall,
Expand All @@ -23,4 +27,18 @@ describe("packages/cli package metadata", () => {
expect(packageJson.devDependencies?.["@clankeroverflow/mcp-logger"]).toBeUndefined();
expect(Object.values(packageJson.dependencies ?? {})).not.toContain("catalog:");
});

test("publishes an OpenClaw-compatible ClawHub bundle", () => {
expect(codexPluginJson.name).toBe("clankeroverflow");
expect(codexPluginJson.version).toBe(packageJson.version);
expect(codexPluginJson.skills).toBe("./skills/");
expect(codexPluginJson.mcpServers).toBe("./.mcp.json");
expect(openClawPluginJson.id).toBe("clankeroverflow");
expect(openClawPluginJson.version).toBe(packageJson.version);
expect(openClawPluginJson.skills).toEqual(["./skills"]);
expect(openClawPluginJson.configSchema).toEqual({
type: "object",
additionalProperties: false,
});
});
});
19 changes: 14 additions & 5 deletions packages/cli/src/plugin/generate-plugin-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,19 @@ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "

const pkg = JSON.parse(await readFile(path.join(packageRoot, "package.json"), "utf-8"));

const pluginJsonPath = path.join(packageRoot, ".claude-plugin", "plugin.json");
const pluginJson = JSON.parse(await readFile(pluginJsonPath, "utf-8"));
const pluginJsonPaths = [
path.join(packageRoot, ".claude-plugin", "plugin.json"),
path.join(packageRoot, ".codex-plugin", "plugin.json"),
path.join(packageRoot, "openclaw.plugin.json"),
];

pluginJson.version = pkg.version;
for (const pluginJsonPath of pluginJsonPaths) {
const pluginJson = JSON.parse(await readFile(pluginJsonPath, "utf-8"));

await writeFile(pluginJsonPath, JSON.stringify(pluginJson, null, 2) + "\n");
console.log(`Stamped plugin.json version: ${pluginJson.version}`);
pluginJson.version = pkg.version;

await writeFile(pluginJsonPath, JSON.stringify(pluginJson, null, 2) + "\n");
console.log(
`Stamped ${path.relative(packageRoot, pluginJsonPath)} version: ${pluginJson.version}`,
);
}
Loading