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 .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"name": "interactor-cms",
"source": "./plugins/interactor-cms",
"description": "Write and edit Interactor CMS posts from Claude: open a post and type right on it, formatted, beside the chat, or ask Claude for changes.",
"version": "1.1.0"
"version": "1.2.0"
}
]
}
75 changes: 75 additions & 0 deletions .github/workflows/humanizer-update.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Humanizer update

# Checks https://github.com/blader/humanizer every day and, when its SKILL.md
# changed, opens (or refreshes) one PR that copies it into the plugin's
# `humanizer` skill and bumps the plugin's patch version. A PR rather than a
# direct push: it is third-party text every writer's Claude follows, so a
# person reads the diff first.
#
# PRs opened with the default GITHUB_TOKEN don't trigger test.yml, so this job
# runs the tests itself. Add a HUMANIZER_PR_TOKEN secret (a PAT with contents +
# pull-requests write) to have them run on the PR too. The repo setting "Allow
# GitHub Actions to create and approve pull requests" must be on.
on:
schedule:
- cron: "23 14 * * *"
workflow_dispatch:

permissions:
contents: write
pull-requests: write

concurrency:
group: humanizer-update
cancel-in-progress: false

jobs:
update:
runs-on: ubuntu-latest
env:
BRANCH: chore/humanizer-sync
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20

- id: sync
run: node scripts/sync-humanizer.mjs
env:
GITHUB_TOKEN: ${{ github.token }}

- if: steps.sync.outputs.changed == 'true'
run: node --test tests/live-sync.test.mjs tests/check-reply.test.mjs tests/humanizer.test.mjs

- if: steps.sync.outputs.changed == 'true'
env:
GH_TOKEN: ${{ secrets.HUMANIZER_PR_TOKEN || github.token }}
VERSION: ${{ steps.sync.outputs.version }}
PREVIOUS: ${{ steps.sync.outputs.previous_version }}
COMMIT: ${{ steps.sync.outputs.commit }}
CHANGES: ${{ steps.sync.outputs.changes }}
PLUGIN_VERSION: ${{ steps.sync.outputs.plugin_version }}
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git switch -C "$BRANCH"
git add plugins .claude-plugin
git commit -m "Update Humanizer to v$VERSION; version $PLUGIN_VERSION"
git push --force origin "$BRANCH"
{
echo "Upstream [blader/humanizer](https://github.com/blader/humanizer/commit/$COMMIT) changed its SKILL.md: v$PREVIOUS -> v$VERSION. Plugin version -> $PLUGIN_VERSION."
echo
echo "Every writer's Claude follows this skill. Read the diff of \`skills/humanizer/SKILL.md\` before merging. The CMS server gets the same update through its own PR (InteractorOSS/content-manager)."
echo
echo "## Upstream changelog"
echo
echo "${CHANGES:-_No changelog entry._}"
} > pr-body.md
title="Update Humanizer to v$VERSION"
if gh pr view "$BRANCH" --json state -q .state 2>/dev/null | grep -q OPEN; then
gh pr edit "$BRANCH" --title "$title" --body-file pr-body.md
else
gh pr create --head "$BRANCH" --base main --title "$title" --body-file pr-body.md
fi
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 18
- run: node --test tests/live-sync.test.mjs tests/check-reply.test.mjs
- run: node --test tests/live-sync.test.mjs tests/check-reply.test.mjs tests/humanizer.test.mjs
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,25 @@ one post, expires after 8 hours, and stops working when the Claude connection is
under **API Tokens** in the CMS. The sync tool (`plugins/interactor-cms/scripts/live-sync.mjs`)
has no dependencies and talks only to the CMS.

## Writing like a person (Humanizer)

The plugin includes [Humanizer](https://github.com/blader/humanizer) (MIT, by Siqi Chen) as
its `humanizer` skill, copied verbatim with its license into
`plugins/interactor-cms/skills/humanizer/`. Claude makes a last pass with it over any post,
excerpt, search description, social copy or FAQ answer it writes, so the result doesn't read
like a chatbot. The CMS server holds the same rules for its own AI drafting and for every
other client of its connector.

`node scripts/sync-humanizer.mjs --check` says whether upstream changed (exit 10 when it
has); without `--check` it copies the new version in and bumps the plugin's patch version so
installed copies update. `.github/workflows/humanizer-update.yml` runs it daily and opens a
pull request; read the diff before merging, since it is third-party text every writer's
Claude follows.

## Development

```
node --test tests/live-sync.test.mjs
node --test tests/live-sync.test.mjs tests/check-reply.test.mjs tests/humanizer.test.mjs
claude plugin validate .
```

Expand Down
2 changes: 1 addition & 1 deletion plugins/interactor-cms/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "interactor-cms",
"displayName": "Interactor CMS",
"description": "Write and edit Interactor CMS posts from Claude: open a post and type right on it, formatted, beside the chat, or ask Claude for changes. Includes the Interactor CMS connection: sign in once, nothing else to set up.",
"version": "1.1.0",
"version": "1.2.0",
"author": {
"name": "Interactor"
},
Expand Down
1 change: 1 addition & 0 deletions plugins/interactor-cms/hooks/session-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ The Interactor CMS plugin is installed.
- **Open pages so the writer can find them again.** Open `list_url` and `editor_url` with the browser action that opens a URL in the panel (in the Claude desktop app: `preview_start` with `url`), not `navigate`: only opening shows the "Opened in Browser" card the writer clicks to bring the panel back.
- **Open it; don't offer to.** After you create a post, when the post the writer asked for already exists, and when they answer a list with a number or a title: open that post in the app's browser panel right away (`open_for_editing`, then your browser tool on its `editor_url`). Never end with "want me to open it?" or leave them a link to click instead: a chat link opens their system browser, not the panel.
- **Every post is written to be found** (search engines, answer boxes, AI assistants). When you draft or substantially rewrite a post, or before `post_workflow` submit or publish, use the `interactor-cms:optimize-post` skill and give the writer its short report. Never invent statistics, quotes or sources: ask the writer.
- **Every post reads like a person wrote it.** Before you save prose you wrote or rewrote (a post, its title, excerpt or search description, social copy, a FAQ answer), make a last pass with the `interactor-cms:humanizer` skill in embedded mode: remove the AI-writing patterns, keep every fact, add none. The writer's own words and style decide the voice.
- **Deleting goes to the trash.** `delete_post` (and "Move to trash" in the editor) keeps the post in the trash for 30 days; the writer sees it under the post list's **Trash** tab. To bring one back, `list_deleted_posts`, then `restore_deleted_post` with the entry's `id`: it returns as a draft (publish again with `post_workflow` only if asked), then open it like any other post. Say it went to the trash when you delete, not that it's gone for good.
- The editor's **Details** panel holds the featured image, URL slug, category, tags, sites and search description; its **Social** tab holds the LinkedIn, X and Facebook copy (write those with `save_social_draft`, see the write-post skill). The writer adds pictures by pasting or dropping them into the text.
- **Another folder may already have it.** If the writer asks for an account, organization or site that another folder is bound to (see "bound folders" below), say so and offer to switch to that folder; if they say yes and you have a tool that changes the session's folder, use it; otherwise tell them to start a session there. To bind this folder ("use this folder for psdjung@gmail.com, Peter Jung, peterjung.site"), follow "Bind this folder" in the write-post skill.
Expand Down
21 changes: 21 additions & 0 deletions plugins/interactor-cms/skills/humanizer/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2025 Siqi Chen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Loading
Loading