From e41dc9b5e5da41a93eb0a1bb4dbe8d5b8f448b5f Mon Sep 17 00:00:00 2001 From: Gilad Resisi Date: Mon, 20 Jul 2026 20:36:42 +0700 Subject: [PATCH] feat: add posts:settings command to patch a post's provider settings Add `postiz posts:settings --settings ''` calling PUT /public/v1/posts/:id/settings to patch a post's provider-specific settings without recreating it. Modeled on the existing posts:status command. Settings are merged server-side (only passed keys change); only DRAFT/QUEUE posts can be updated. Also document that posts:list responses now include each post's current `settings` (returned as a JSON string). Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 8 ++++++++ README.md | 12 +++++++++++- SKILL.md | 10 ++++++++++ src/api.ts | 7 +++++++ src/commands/posts.ts | 33 +++++++++++++++++++++++++++++++++ src/index.ts | 27 ++++++++++++++++++++++++++- 6 files changed, 95 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8221956..3869442 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to the Postiz CLI will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added +- `posts:settings` - Update a post's provider settings via `PUT /public/v1/posts/:id/settings` (merged — only the keys you pass change; unpublished DRAFT/QUEUE posts only). + +### Changed +- `posts:list` responses now include each post's current `settings`. + ## [1.0.0] - 2026-02-13 ### Added diff --git a/README.md b/README.md index 7dfceca..9799a4c 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,7 @@ postiz posts:list --startDate "2024-01-01T00:00:00Z" --endDate "2024-12-31T23:59 postiz posts:list --customer "customer-id" ``` -Defaults to last 30 days to next 30 days if dates not specified. +Defaults to last 30 days to next 30 days if dates not specified. Each returned post includes its current `settings` (returned as a JSON string — `JSON.parse` it). The intended workflow is `posts:list` (read current settings) → `posts:settings` (patch them). **Delete post** ```bash @@ -198,6 +198,14 @@ postiz posts:status --status schedule Move a scheduled post back to a draft, or promote a draft into the publishing queue. Switching to `draft` also terminates any workflow that's already running for the post, so it won't publish. Switching to `schedule` queues the post for publishing at its stored date. +**Update a post's provider-specific settings** +```bash +postiz posts:settings --settings '{"content_posting_method":"DIRECT_POST"}' +postiz posts:settings --settings '{"subreddit":[{"value":{"subreddit":"/r/selfhosted","title":"My title","type":"self","is_flair_required":true}}]}' +``` + +Patches a post's settings server-side. The backend **merges** the object — only the keys you pass change, everything else is preserved — so pass a partial object, not the full settings blob. Only **DRAFT/QUEUE** (unpublished) posts can be updated; published posts are rejected. Pass the **main post id**, not a comment id. Do **not** include `__type` — the backend adds it automatically from the integration. + --- ### Analytics @@ -560,6 +568,7 @@ The CLI interacts with these Postiz API endpoints: | `/public/v1/posts` | POST | Create a post | | `/public/v1/posts` | GET | List posts | | `/public/v1/posts/:id` | DELETE | Delete a post | +| `/public/v1/posts/:id/settings` | PUT | Update a post's provider settings (merged; unpublished only) | | `/public/v1/posts/:id/missing` | GET | Get missing content from provider | | `/public/v1/posts/:id/release-id` | PUT | Update release ID for a post | | `/public/v1/integrations` | GET | List integrations (optional `?group=` filter) | @@ -679,6 +688,7 @@ postiz posts:list # List posts postiz posts:delete # Delete post postiz posts:status --status draft # Move to draft (stops workflow) postiz posts:status --status schedule # Queue draft for publishing +postiz posts:settings --settings '{}' # Patch a post's settings (merged; DRAFT/QUEUE only) postiz upload # Upload media # Analytics diff --git a/SKILL.md b/SKILL.md index 8915e3a..13d2602 100644 --- a/SKILL.md +++ b/SKILL.md @@ -194,6 +194,8 @@ postiz posts:create --json post.json ```bash # List posts (defaults to last 30 days to next 30 days) +# Each returned post includes its current `settings` (as a JSON string — JSON.parse it). +# Workflow: run posts:list to read a post's current settings, then posts:settings to patch them. postiz posts:list # List posts in date range @@ -205,6 +207,12 @@ postiz posts:delete # Change post status (draft ↔ schedule) postiz posts:status --status draft # Move back to draft, terminates any running publish workflow postiz posts:status --status schedule # Promote a draft into the publishing queue (uses the post's stored date) + +# Update a post's provider-specific settings (merged — only the keys you pass change) +# Only DRAFT/QUEUE (unpublished) posts can be updated. Pass the MAIN post id, not a comment id. +# Do NOT include __type — the backend adds it automatically from the integration. +postiz posts:settings --settings '{"content_posting_method":"DIRECT_POST"}' # Switch a TikTok draft to direct publishing +postiz posts:settings --settings '{"subreddit":[{"value":{"subreddit":"/r/selfhosted","title":"My title","type":"self","is_flair_required":true}}]}' # Set a Reddit post's subreddit ``` ### Analytics @@ -749,6 +757,7 @@ https://clawhub.ai/nevo-david/agent-media 9. **Required settings** - Some platforms require specific settings (Reddit needs title, YouTube needs title) 10. **Media MIME types** - CLI auto-detects from file extension, ensure correct extension 11. **Analytics returns `{"missing": true}`** - The post was published but the platform didn't return a post ID. Run `posts:missing ` to get available content, then `posts:connect --release-id ""` to link it. Analytics will work after connecting. +12. **`posts:settings` merges** - Only the keys you pass change; everything else on the post is preserved, so pass a partial object, not the full settings blob. Only **DRAFT/QUEUE** (unpublished) posts can be updated — published posts are rejected. Pass the **main post id**, not a comment id. Never include `__type` — the backend adds it automatically from the integration. --- @@ -781,6 +790,7 @@ postiz posts:list # List posts postiz posts:delete # Delete post postiz posts:status --status draft # Move to draft (stops workflow) postiz posts:status --status schedule # Queue draft for publishing +postiz posts:settings --settings '{}' # Patch a post's settings (merged; DRAFT/QUEUE only) postiz upload # Upload media # Analytics diff --git a/src/api.ts b/src/api.ts index eba70fd..e64db74 100644 --- a/src/api.ts +++ b/src/api.ts @@ -157,6 +157,13 @@ export class PostizAPI { }); } + async updatePostSettings(postId: string, settings: Record) { + return this.request(`/public/v1/posts/${postId}/settings`, { + method: 'PUT', + body: JSON.stringify({ settings }), + }); + } + async getAnalytics(integrationId: string, date: string) { return this.request(`/public/v1/analytics/${integrationId}?date=${encodeURIComponent(date)}`, { method: 'GET', diff --git a/src/commands/posts.ts b/src/commands/posts.ts index c501262..fb53919 100644 --- a/src/commands/posts.ts +++ b/src/commands/posts.ts @@ -205,6 +205,39 @@ export async function changePostStatus(args: any) { } } +export async function updatePostSettings(args: any) { + const config = getConfig(); + const api = new PostizAPI(config); + + if (!args.id) { + console.error('❌ Post ID is required'); + process.exit(1); + } + + if (!args.settings) { + console.error('❌ --settings is required'); + process.exit(1); + } + + let settings: any; + try { + settings = JSON.parse(args.settings); + } catch (error: any) { + console.error('❌ Failed to parse settings JSON:', error.message); + process.exit(1); + } + + try { + const result = await api.updatePostSettings(args.id, settings); + console.log(`✅ Post ${args.id} settings updated`); + console.log(JSON.stringify(result, null, 2)); + return result; + } catch (error: any) { + console.error('❌ Failed to update post settings:', error.message); + process.exit(1); + } +} + export async function deletePost(args: any) { const config = getConfig(); const api = new PostizAPI(config); diff --git a/src/index.ts b/src/index.ts index 430c581..0d08ef9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; -import { createPost, listPosts, deletePost, getMissingContent, connectPost, changePostStatus } from './commands/posts'; +import { createPost, listPosts, deletePost, getMissingContent, connectPost, changePostStatus, updatePostSettings } from './commands/posts'; import { listIntegrations, listGroups, getIntegrationSettings, triggerIntegrationTool } from './commands/integrations'; import { getAnalytics, getPostAnalytics } from './commands/analytics'; import { uploadFile } from './commands/upload'; @@ -202,6 +202,31 @@ yargs(hideBin(process.argv)) }, changePostStatus as any ) + .command( + 'posts:settings ', + 'Update a post\'s provider-specific settings (merged; only unpublished draft/scheduled posts)', + (yargs: Argv) => { + return yargs + .positional('id', { + describe: 'Post ID', + type: 'string', + }) + .option('settings', { + describe: 'Partial settings as a JSON string — only the keys you pass change; do not include __type', + type: 'string', + demandOption: true, + }) + .example( + '$0 posts:settings post-123 --settings \'{"content_posting_method":"DIRECT_POST"}\'', + 'Switch a TikTok draft to direct publishing' + ) + .example( + '$0 posts:settings post-123 --settings \'{"subreddit":[{"value":{"subreddit":"/r/selfhosted","title":"My title","type":"self","is_flair_required":true}}]}\'', + 'Set a Reddit post\'s subreddit' + ); + }, + updatePostSettings as any + ) .command( 'posts:connect ', 'Connect a post to its published content by updating the release ID',