mirror of
https://github.com/gitroomhq/postiz-agent.git
synced 2026-09-14 13:46:24 +08:00
feat: DeepSeek Harness plugin (dsh-postiz)
Add plugins/dsh-postiz, an installable DeepSeek Harness bundle (`dsh plugin --profile web add "github:gitroomhq/postiz-agent#path:/plugins/dsh-postiz"`). - cordis.patch.yml mounts a `postiz` provider row plus one @deepseek-ai/dsh-mcp-client row pointed at https://mcp.postiz.com/mcp (streamable HTTP, Bearer auth from POSTIZ_API_KEY; baseUrl overridable for self-hosted instances) - the provider exposes ctx.postiz (url/headers) for the mcp row's !!js config and registers a `postiz` skill covering the integrationList → integrationSchema → schedulePostTool workflow and HTML content rules - node:test unit tests; verified against dsh 0.1.5-rc: the mcp row activates, sends the Bearer header, and retries on auth failure - README / CHANGELOG entries; ignore plugin-local lockfiles Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -36,3 +36,6 @@ coverage/
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
|
||||
# Plugin subpackages install their peer deps locally for tests
|
||||
plugins/*/pnpm-lock.yaml
|
||||
|
||||
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- DeepSeek Harness plugin: `plugins/dsh-postiz` is an installable `dsh` bundle (`dsh plugin --profile web add dsh-postiz`). It mounts one `@deepseek-ai/dsh-mcp-client` row pointed at the hosted Postiz MCP server (`https://mcp.postiz.com/mcp`, Bearer auth from `POSTIZ_API_KEY`) and registers a `postiz` skill describing the integrationList → integrationSchema → schedulePostTool workflow. Self-hosted instances override `baseUrl` on the `postiz` row. Listed on [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin); the repo now carries the `dsh-plugin` topic.
|
||||
|
||||
## [2.0.17] - 2026-09-01
|
||||
|
||||
### Added
|
||||
|
||||
@@ -37,6 +37,18 @@ This repo ships a [Cursor plugin](https://cursor.com/docs/reference/plugins) man
|
||||
|
||||
The plugin exposes the `postiz` skill, which drives the `postiz` CLI (the CLI handles media uploads, which is required for image/video posts). Make sure the CLI is installed (`npm install -g postiz`) and authenticated (`postiz auth:login` or `export POSTIZ_API_KEY=...`) before asking the agent to post.
|
||||
|
||||
### DeepSeek Harness plugin
|
||||
|
||||
This repo ships a [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh`) bundle at [`plugins/dsh-postiz`](plugins/dsh-postiz). It connects the agent to the hosted Postiz MCP server and registers a `postiz` workflow skill.
|
||||
|
||||
```bash
|
||||
dsh plugin --profile web add "github:gitroomhq/postiz-agent#path:/plugins/dsh-postiz"
|
||||
export POSTIZ_API_KEY=your-api-key # Postiz → Settings → Developers → Public API
|
||||
dsh web
|
||||
```
|
||||
|
||||
The Postiz tools then appear as `mcp__postiz__*` (`integrationList`, `integrationSchema`, `schedulePostTool`, ...). Self-hosted instances override `baseUrl` on the `postiz` row. See the [plugin README](plugins/dsh-postiz/README.md) for configuration.
|
||||
|
||||
# Postiz CLI
|
||||
|
||||
**Social media automation CLI for AI agents** - Schedule posts across 28+ platforms programmatically.
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2024 Nevo David
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
@@ -0,0 +1,117 @@
|
||||
# dsh-postiz
|
||||
|
||||
[](https://www.npmjs.com/package/dsh-postiz)
|
||||
|
||||
**npm:** [`dsh-postiz`](https://www.npmjs.com/package/dsh-postiz) ·
|
||||
**source:** [gitroomhq/postiz-agent](https://github.com/gitroomhq/postiz-agent/tree/main/plugins/dsh-postiz)
|
||||
|
||||
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh`) plugin for
|
||||
[Postiz](https://postiz.com), the open-source social media scheduler. It connects the agent
|
||||
to the Postiz MCP server so it can list your connected channels, fetch each platform's
|
||||
posting rules, and schedule, draft, or publish posts across 28+ platforms (X, LinkedIn,
|
||||
Instagram, Facebook, Threads, TikTok, YouTube, Reddit, Pinterest, Bluesky, Mastodon,
|
||||
Discord, Slack, Telegram, and more).
|
||||
|
||||
## Install
|
||||
|
||||
From the repository:
|
||||
|
||||
```bash
|
||||
dsh plugin --profile web add "github:gitroomhq/postiz-agent#path:/plugins/dsh-postiz"
|
||||
```
|
||||
|
||||
Or from npm:
|
||||
|
||||
```bash
|
||||
dsh plugin --profile web add dsh-postiz
|
||||
```
|
||||
|
||||
Then give it a Postiz API key. Copy it from **Postiz → Settings → Developers → Public API**
|
||||
and export it before starting dsh, or put it in `$DSH_HOME/.env`:
|
||||
|
||||
```bash
|
||||
export POSTIZ_API_KEY=your-api-key
|
||||
dsh web
|
||||
```
|
||||
|
||||
Restart the profile after installing. Ask the agent *"List my connected social media
|
||||
accounts"* to verify the connection.
|
||||
|
||||
## What you get
|
||||
|
||||
The bundle mounts two rows:
|
||||
|
||||
| Row | Package | Role |
|
||||
|---|---|---|
|
||||
| `postiz` | `dsh-postiz` (this package) | Reads the API key, exposes `ctx.postiz` (`url`, `headers`), and registers the `postiz` skill that teaches the agent the posting workflow and the HTML content rules. |
|
||||
| `postiz-mcp` | `@deepseek-ai/dsh-mcp-client` (shipped with dsh) | Connects to `https://mcp.postiz.com/mcp` over streamable HTTP with a `Bearer` header and registers the server's tools. |
|
||||
|
||||
The model sees the Postiz MCP tools under the `mcp__postiz__` namespace:
|
||||
|
||||
| Tool | What it does |
|
||||
|---|---|
|
||||
| `mcp__postiz__integrationList` | List connected channels (optionally filtered by group) |
|
||||
| `mcp__postiz__groupList` | List customer groups |
|
||||
| `mcp__postiz__integrationSchema` | Posting rules, character limits, and required settings for a platform |
|
||||
| `mcp__postiz__triggerTool` | Platform helpers (list Discord channels, search subreddits, list LinkedIn pages) |
|
||||
| `mcp__postiz__schedulePostTool` | Schedule, draft, or immediately publish posts |
|
||||
| `mcp__postiz__postsListTool` | List posts scheduled between two dates |
|
||||
| `mcp__postiz__postSettingsTool` | Update settings of an unpublished post |
|
||||
| `mcp__postiz__generateImageTool` | Generate an image for a post |
|
||||
| `mcp__postiz__generateVideoOptions` / `videoFunctionTool` / `generateVideoTool` | Video generation options and generation |
|
||||
|
||||
The tool list comes from the server at connect time, so new Postiz tools appear without a
|
||||
plugin update. See the [Postiz MCP tools reference](https://docs.postiz.com/mcp/tools).
|
||||
|
||||
## Configuration
|
||||
|
||||
Override the `postiz` row in your profile's `cordis.patch.yml` (`$DSH_HOME/profiles/web/cordis.patch.yml`).
|
||||
A bare `id:` configures the existing row:
|
||||
|
||||
```yaml
|
||||
- id: postiz
|
||||
config:
|
||||
apiKeyEnv: POSTIZ_API_KEY # env var that holds the key (default)
|
||||
baseUrl: https://mcp.postiz.com # self-hosted: https://your-postiz-server.com
|
||||
skill: true # register the `postiz` workflow skill
|
||||
```
|
||||
|
||||
| Field | Default | Description |
|
||||
|---|---|---|
|
||||
| `apiKeyEnv` | `POSTIZ_API_KEY` | Environment variable read at boot for the API key. |
|
||||
| `apiKey` | `''` | Inline key. Prefer the env var; this exists for patch-level overrides. |
|
||||
| `baseUrl` | `https://mcp.postiz.com` | Postiz host. The MCP endpoint is `<baseUrl>/mcp`. Self-hosted instances point this at their backend. |
|
||||
| `skill` | `true` | Register the `postiz` skill on `ctx.skills`. |
|
||||
|
||||
Without a key the `postiz` row logs a warning naming the variable to set, and the
|
||||
`postiz-mcp` row registers no tools. dsh keeps booting.
|
||||
|
||||
## Self-hosted Postiz
|
||||
|
||||
The MCP server is part of the Postiz backend and listens at `/mcp` (Bearer auth). Point
|
||||
`baseUrl` at your backend and make sure your reverse proxy forwards `/mcp` with streaming
|
||||
HTTP enabled. See [Reverse Proxies](https://docs.postiz.com/self-host/reverse-proxies/caddy).
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
cd plugins/dsh-postiz
|
||||
pnpm install
|
||||
pnpm test
|
||||
```
|
||||
|
||||
Link a local checkout into a profile:
|
||||
|
||||
```bash
|
||||
dsh plugin --profile web add /absolute/path/to/postiz-agent/plugins/dsh-postiz
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Postiz CLI](https://github.com/gitroomhq/postiz-agent) — the `postiz` command-line tool and the Claude Code / Cursor / Grok plugins in this repository.
|
||||
- [Postiz MCP docs](https://docs.postiz.com/mcp/introduction)
|
||||
- [Postiz public API](https://docs.postiz.com/public-api/introduction)
|
||||
|
||||
## License
|
||||
|
||||
AGPL-3.0, same as the rest of this repository.
|
||||
@@ -0,0 +1,38 @@
|
||||
# Patch layer contributed by the dsh-postiz bundle.
|
||||
#
|
||||
# Two rows:
|
||||
# 1. `postiz` - this package's provider plugin. Reads the Postiz API key
|
||||
# (POSTIZ_API_KEY by default), exposes `ctx.postiz` with the
|
||||
# MCP endpoint URL and auth headers, and registers the
|
||||
# `postiz` skill that teaches the agent the posting workflow.
|
||||
# 2. `postiz-mcp` - one @deepseek-ai/dsh-mcp-client instance that connects to
|
||||
# the Postiz MCP server over streamable HTTP and registers
|
||||
# its tools as mcp__postiz__* (integrationList,
|
||||
# integrationSchema, schedulePostTool, ...).
|
||||
#
|
||||
# Configuration (override the `postiz` row in your profile's cordis.patch.yml):
|
||||
# apiKeyEnv - env var holding the Postiz API key (default: POSTIZ_API_KEY)
|
||||
# apiKey - inline key; prefer the env var or a .env file in $DSH_HOME
|
||||
# baseUrl - Postiz host (default: https://mcp.postiz.com; self-hosted
|
||||
# instances use their own backend URL)
|
||||
# skill - register the `postiz` workflow skill (default: true)
|
||||
- insert:
|
||||
- id: postiz
|
||||
name: dsh-postiz
|
||||
config:
|
||||
apiKeyEnv: POSTIZ_API_KEY
|
||||
baseUrl: https://mcp.postiz.com
|
||||
- id: postiz-mcp
|
||||
name: '@deepseek-ai/dsh-mcp-client'
|
||||
inject: [postiz]
|
||||
config:
|
||||
transport: streamable-http
|
||||
serverName: postiz
|
||||
url: !!js ctx.postiz.url
|
||||
headers: !!js ctx.postiz.headers
|
||||
failOnStartupError: false
|
||||
reconnect:
|
||||
enabled: true
|
||||
initialDelayMs: 2000
|
||||
maxDelayMs: 30000
|
||||
maxAttempts: 10
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* dsh-postiz provider plugin.
|
||||
*
|
||||
* Provides the `postiz` service so the bundle's cordis.patch.yml can point one
|
||||
* `@deepseek-ai/dsh-mcp-client` row at the Postiz MCP server without putting
|
||||
* the API key in configuration: the mcp-client row injects `postiz` and reads
|
||||
* `ctx.postiz.url` / `ctx.postiz.headers` in its `!!js` config.
|
||||
*
|
||||
* It also registers the `postiz` skill on `ctx.skills` (when a skills service
|
||||
* is mounted) so the agent knows the integrationList → integrationSchema →
|
||||
* schedulePostTool workflow and the HTML content rules.
|
||||
*
|
||||
* Named exports only (no default export): a stray default export makes the
|
||||
* Loader collapse the namespace and drop `inject`/`apply`.
|
||||
*/
|
||||
import z from '@deepseek-ai/schemastery'
|
||||
import { SKILL_CONTENT, SKILL_DESCRIPTION, SKILL_NAME } from './skill.js'
|
||||
|
||||
/** Stable Cordis plugin name. */
|
||||
export const name = 'dsh-postiz'
|
||||
|
||||
/** Service provided by this plugin and injected by the mcp-client row. */
|
||||
export const POSTIZ_SERVICE = 'postiz'
|
||||
|
||||
/** Default Postiz Cloud host; self-hosted instances override `baseUrl`. */
|
||||
export const DEFAULT_BASE_URL = 'https://mcp.postiz.com'
|
||||
|
||||
export const Config = z.object({
|
||||
/** Environment variable that holds the Postiz API key. */
|
||||
apiKeyEnv: z.string().default('POSTIZ_API_KEY'),
|
||||
/** Inline API key. Prefer `apiKeyEnv`; this exists for patch-level overrides. */
|
||||
apiKey: z.string().role('secret').default(''),
|
||||
/** Postiz host. The MCP endpoint is `<baseUrl>/mcp`. */
|
||||
baseUrl: z.string().default(DEFAULT_BASE_URL),
|
||||
/** Register the `postiz` workflow skill on `ctx.skills`. */
|
||||
skill: z.boolean().default(true),
|
||||
})
|
||||
|
||||
/**
|
||||
* Resolve the connection values from config and environment.
|
||||
*
|
||||
* @param {object} config - validated plugin config.
|
||||
* @param {Record<string, string | undefined>} env - environment to read the key from.
|
||||
* @returns {{ url: string, headers: Record<string, string>, configured: boolean, source: string }}
|
||||
*/
|
||||
export function resolveConnection(config, env = process.env) {
|
||||
const base = String(config?.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, '')
|
||||
const url = base.endsWith('/mcp') ? base : `${base}/mcp`
|
||||
const envName = String(config?.apiKeyEnv || 'POSTIZ_API_KEY')
|
||||
const inline = String(config?.apiKey || '').trim()
|
||||
const fromEnv = String(env[envName] || '').trim()
|
||||
const key = inline || fromEnv
|
||||
const source = inline ? 'config.apiKey' : fromEnv ? `env.${envName}` : 'none'
|
||||
return {
|
||||
url,
|
||||
headers: key ? { Authorization: `Bearer ${key}` } : {},
|
||||
configured: key.length > 0,
|
||||
source,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} ctx - plugin context.
|
||||
* @param {object} config - validated plugin config.
|
||||
*/
|
||||
export function apply(ctx, config) {
|
||||
const cfg = Config(config ?? {})
|
||||
const conn = resolveConnection(cfg)
|
||||
const log = ctx.logger ?? console
|
||||
|
||||
if (conn.configured) {
|
||||
log.info?.(`dsh-postiz: Postiz MCP endpoint ${conn.url} (key from ${conn.source})`)
|
||||
} else {
|
||||
log.warn?.(
|
||||
`dsh-postiz: no Postiz API key found — set ${cfg.apiKeyEnv} (Postiz → Settings → Developers → Public API) ` +
|
||||
`or \`apiKey\` on the \`postiz\` row; the postiz-mcp row will connect to ${conn.url} without credentials and register no tools.`,
|
||||
)
|
||||
}
|
||||
|
||||
// Plain, unfrozen values on purpose: the mcp-client row validates the
|
||||
// interpolated `headers` with schemastery, which rejects frozen objects.
|
||||
// The getter hands every evaluation its own copy.
|
||||
ctx.provide(POSTIZ_SERVICE, {
|
||||
url: conn.url,
|
||||
get headers() {
|
||||
return { ...conn.headers }
|
||||
},
|
||||
configured: conn.configured,
|
||||
})
|
||||
|
||||
if (cfg.skill) {
|
||||
ctx.inject(['skills'], (sctx) => {
|
||||
if (typeof sctx.skills?.register !== 'function') return
|
||||
sctx.skills.register({
|
||||
name: SKILL_NAME,
|
||||
description: SKILL_DESCRIPTION,
|
||||
content: SKILL_CONTENT,
|
||||
source: 'runtime',
|
||||
invocation: { modelInvocable: true, userInvocable: true },
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* The `postiz` skill body registered on `ctx.skills`.
|
||||
*
|
||||
* It teaches the agent the order of operations for the Postiz MCP tools that
|
||||
* the sibling `postiz-mcp` row exposes as `mcp__postiz__*`. Kept as a plain
|
||||
* string so the plugin ships no extra files and needs no filesystem access.
|
||||
*/
|
||||
|
||||
export const SKILL_NAME = 'postiz'
|
||||
|
||||
export const SKILL_DESCRIPTION =
|
||||
'Schedule, draft, or publish social media posts through Postiz (X, LinkedIn, Instagram, Facebook, Threads, TikTok, YouTube, Reddit, Bluesky, Mastodon, Discord, Slack, Telegram and more). Use when the user wants to post to social media, list connected channels, or plan a content calendar.'
|
||||
|
||||
export const SKILL_CONTENT = `# Postiz
|
||||
|
||||
Postiz is a social media scheduler. The \`mcp__postiz__*\` tools talk to the
|
||||
user's Postiz account. Always follow this order:
|
||||
|
||||
## 1. Find the channel
|
||||
|
||||
Call \`mcp__postiz__integrationList\` to get connected accounts. Each entry has
|
||||
an \`id\` (the integration ID), a \`name\`, and a \`platform\` such as \`x\`,
|
||||
\`linkedin\`, \`instagram\`, \`facebook\`, \`threads\`, \`tiktok\`, \`youtube\`,
|
||||
\`reddit\`, \`bluesky\`, \`mastodon\`, \`discord\`, \`slack\`, \`telegram\`.
|
||||
If the account has customer groups, \`mcp__postiz__groupList\` lists them and
|
||||
\`integrationList\` accepts a \`group\` filter.
|
||||
|
||||
Never guess an integration ID. If the user names a platform that is not in the
|
||||
list, say so and stop.
|
||||
|
||||
## 2. Learn the platform rules
|
||||
|
||||
Call \`mcp__postiz__integrationSchema\` with \`{ platform, isPremium }\` before
|
||||
composing. It returns \`maxLength\`, \`rules\`, a \`settings\` JSON schema of
|
||||
required per-platform settings, and helper \`tools\` (for example: list Discord
|
||||
channels, search subreddits, list LinkedIn pages). Run helpers with
|
||||
\`mcp__postiz__triggerTool\` when a setting needs an ID you do not have.
|
||||
|
||||
## 3. Compose the content
|
||||
|
||||
- Content is HTML. Wrap every line in \`<p>\`. Allowed tags: \`<p>\`, \`<h1>\`,
|
||||
\`<h2>\`, \`<h3>\`, \`<strong>\`, \`<u>\`, \`<ul>\`, \`<li>\`. Do not combine
|
||||
\`<u>\` and \`<strong>\` in one element.
|
||||
- Respect \`maxLength\` from the schema.
|
||||
- \`postsAndComments\` is an array. On thread platforms (X, Threads, Bluesky)
|
||||
each item is a new post in the thread; on comment platforms (LinkedIn,
|
||||
Facebook) the first item is the post and the rest are comments.
|
||||
- \`attachments\` are media URLs. To generate media first, use
|
||||
\`mcp__postiz__generateImageTool\` or \`mcp__postiz__generateVideoTool\`
|
||||
(check \`generateVideoOptions\` and \`videoFunctionTool\` for settings).
|
||||
|
||||
## 4. Schedule or publish
|
||||
|
||||
Call \`mcp__postiz__schedulePostTool\` with a \`socialPost\` array. Each item:
|
||||
|
||||
\`\`\`json
|
||||
{
|
||||
"integrationId": "<id from integrationList>",
|
||||
"isPremium": false,
|
||||
"date": "2025-01-15T10:00:00.000Z",
|
||||
"shortLink": false,
|
||||
"type": "schedule",
|
||||
"postsAndComments": [{ "content": "<p>Hello world</p>", "attachments": [] }],
|
||||
"settings": [{ "key": "<from integrationSchema>", "value": "..." }]
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
- \`type\` is \`draft\`, \`schedule\`, or \`now\`. Use \`draft\` when the user
|
||||
wants to review first; use \`now\` only when they explicitly ask to publish
|
||||
immediately.
|
||||
- \`date\` is UTC ISO-8601. Convert the user's local time; if no time is given,
|
||||
ask or pick a sensible one and state it.
|
||||
- One item per channel per time slot. Twenty posts across a month is twenty
|
||||
items in one call.
|
||||
|
||||
## 5. Review and adjust
|
||||
|
||||
\`mcp__postiz__postsListTool\` lists posts between two dates.
|
||||
\`mcp__postiz__postSettingsTool\` updates the settings of a post that has not
|
||||
been published yet.
|
||||
|
||||
## Setup
|
||||
|
||||
The tools are missing when no API key is configured. The key comes from the
|
||||
\`POSTIZ_API_KEY\` environment variable (Postiz → Settings → Developers →
|
||||
Public API). Self-hosted instances set \`baseUrl\` on the \`postiz\` row in the
|
||||
profile's \`cordis.patch.yml\`.
|
||||
`
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "dsh-postiz",
|
||||
"version": "0.1.0",
|
||||
"description": "DeepSeek Harness plugin that connects the agent to Postiz over MCP: list connected channels, fetch platform posting rules, and schedule, draft, or publish social media posts across 28+ platforms.",
|
||||
"type": "module",
|
||||
"main": "./lib/index.js",
|
||||
"exports": {
|
||||
".": "./lib/index.js",
|
||||
"./package.json": "./package.json",
|
||||
"./cordis.patch.yml": "./cordis.patch.yml"
|
||||
},
|
||||
"files": [
|
||||
"lib/",
|
||||
"cordis.patch.yml",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "node --test"
|
||||
},
|
||||
"dsh": {
|
||||
"bundle": {
|
||||
"patch": "./cordis.patch.yml"
|
||||
}
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/cordis": "^4.0.1",
|
||||
"@deepseek-ai/schemastery": "^3.18.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"dsh",
|
||||
"dsh-plugin",
|
||||
"deepseek-harness",
|
||||
"postiz",
|
||||
"mcp",
|
||||
"social-media",
|
||||
"social-media-scheduler",
|
||||
"scheduling",
|
||||
"automation"
|
||||
],
|
||||
"author": {
|
||||
"name": "Nevo David",
|
||||
"email": "nevo@postiz.com",
|
||||
"url": "https://postiz.com"
|
||||
},
|
||||
"homepage": "https://github.com/gitroomhq/postiz-agent/tree/main/plugins/dsh-postiz#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/gitroomhq/postiz-agent.git",
|
||||
"directory": "plugins/dsh-postiz"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/gitroomhq/postiz-agent/issues"
|
||||
},
|
||||
"license": "AGPL-3.0",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { dirname, join } from 'node:path'
|
||||
|
||||
import * as plugin from '../lib/index.js'
|
||||
import { SKILL_CONTENT, SKILL_NAME } from '../lib/skill.js'
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
test('named exports only, with a stable plugin name', () => {
|
||||
assert.equal(plugin.name, 'dsh-postiz')
|
||||
assert.equal(typeof plugin.apply, 'function')
|
||||
assert.equal('default' in plugin, false)
|
||||
})
|
||||
|
||||
test('resolveConnection reads the key from the configured env var', () => {
|
||||
const conn = plugin.resolveConnection(
|
||||
{ apiKeyEnv: 'POSTIZ_API_KEY', apiKey: '', baseUrl: 'https://mcp.postiz.com' },
|
||||
{ POSTIZ_API_KEY: 'abc123' },
|
||||
)
|
||||
assert.equal(conn.url, 'https://mcp.postiz.com/mcp')
|
||||
assert.deepEqual(conn.headers, { Authorization: 'Bearer abc123' })
|
||||
assert.equal(conn.configured, true)
|
||||
assert.equal(conn.source, 'env.POSTIZ_API_KEY')
|
||||
})
|
||||
|
||||
test('resolveConnection prefers an inline key and normalizes self-hosted URLs', () => {
|
||||
const conn = plugin.resolveConnection(
|
||||
{ apiKeyEnv: 'POSTIZ_API_KEY', apiKey: 'inline', baseUrl: 'https://postiz.example.com/' },
|
||||
{ POSTIZ_API_KEY: 'ignored' },
|
||||
)
|
||||
assert.equal(conn.url, 'https://postiz.example.com/mcp')
|
||||
assert.deepEqual(conn.headers, { Authorization: 'Bearer inline' })
|
||||
assert.equal(conn.source, 'config.apiKey')
|
||||
})
|
||||
|
||||
test('resolveConnection reports an unconfigured key without headers', () => {
|
||||
const conn = plugin.resolveConnection({ apiKeyEnv: 'POSTIZ_API_KEY' }, {})
|
||||
assert.equal(conn.configured, false)
|
||||
assert.deepEqual(conn.headers, {})
|
||||
})
|
||||
|
||||
test('apply provides ctx.postiz and registers the skill', () => {
|
||||
const provided = {}
|
||||
const registered = []
|
||||
const ctx = {
|
||||
logger: { info() {}, warn() {} },
|
||||
provide(key, value) { provided[key] = value },
|
||||
inject(deps, cb) {
|
||||
assert.deepEqual(deps, ['skills'])
|
||||
cb({ skills: { register(skill) { registered.push(skill); return () => {} } } })
|
||||
},
|
||||
}
|
||||
plugin.apply(ctx, { apiKey: 'k' })
|
||||
assert.equal(provided.postiz.url, 'https://mcp.postiz.com/mcp')
|
||||
assert.equal(provided.postiz.headers.Authorization, 'Bearer k')
|
||||
assert.equal(provided.postiz.configured, true)
|
||||
assert.equal(registered.length, 1)
|
||||
assert.equal(registered[0].name, SKILL_NAME)
|
||||
assert.equal(registered[0].content, SKILL_CONTENT)
|
||||
assert.match(registered[0].content, /mcp__postiz__schedulePostTool/)
|
||||
})
|
||||
|
||||
test('apply skips the skill when disabled', () => {
|
||||
let injected = false
|
||||
const ctx = { logger: { info() {}, warn() {} }, provide() {}, inject() { injected = true } }
|
||||
plugin.apply(ctx, { apiKey: 'k', skill: false })
|
||||
assert.equal(injected, false)
|
||||
})
|
||||
|
||||
test('bundle manifest points at the patch and the patch wires both rows', () => {
|
||||
const pkg = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8'))
|
||||
assert.equal(pkg.dsh.bundle.patch, './cordis.patch.yml')
|
||||
const patch = readFileSync(join(here, '..', 'cordis.patch.yml'), 'utf8')
|
||||
assert.match(patch, /name: dsh-postiz/)
|
||||
assert.match(patch, /name: '@deepseek-ai\/dsh-mcp-client'/)
|
||||
assert.match(patch, /serverName: postiz/)
|
||||
assert.match(patch, /url: !!js ctx\.postiz\.url/)
|
||||
})
|
||||
Reference in New Issue
Block a user