DotShare for AI agents
Connect Codex or another MCP client to DotShare, then ask it to publish a post, images, a thread, or a Dev.to article to named accounts. The agent supplies content; DotShare handles credentials, platform requests and durable publication receipts. The local MCP server uses stdio and does not require a separate model/API key.
Install and set up
Requires Node.js 22.12+ and an unlocked OS credential store. On Linux, a working Secret Service/keyring session is required. There is no plaintext secret fallback.
Install the CLI and MCP server from npm. For an existing library, follow the upgrade steps before resuming publication.
npm install -g dotshare-cli
dotshare-agent setup
dotshare-agent accounts add personal-mastodon --platform mastodon --name "Personal Mastodon"
dotshare-agent doctor --account personal-mastodonRestart or reconnect Codex after setup. In Codex, ask DotShare to list your local accounts, save a draft for one of them and prepare its preview. To review that draft in VS Code, follow the Publishing Library guide.
You can also download the tarball and install it with npm install -g ./dotshare-cli-3.0.0.tgz. GitHub release
To build and configure from source instead:
npm ci
npm run compile
node bin/dotshare-agent.js setup
node bin/dotshare-agent.js doctor
node bin/dotshare-agent.js accounts add personal-mastodon --platform mastodon --name "Personal Mastodon"
node bin/dotshare-agent.js accounts add work-linkedin --platform linkedin --name "Work LinkedIn"
node bin/dotshare-agent.js accounts listAccount setup prompts hide typed credentials. Enter platform secrets in your
terminal prompts. Existing VS Code accounts are not automatically copied.
Multiple accounts on one platform use
different IDs. To replace an account's credentials, run accounts add with its ID.
accounts remove <id> removes the local account and deletes its credential from the OS keyring.
It does not revoke the token on the platform. Expired/revoked platform tokens must
be replaced using accounts add; the local agent does not run browser OAuth or
automatically refresh user-supplied OAuth tokens.
After packaging/installing the CLI, the same commands are available through
dotshare, dotshare-agent, and the dedicated dotshare-mcp server entrypoint.
Credential fields
| Platform | Fields entered during setup |
|---|---|
accessToken (user OAuth token with posting access) | |
| X | accessToken (user OAuth token; media also requires media upload access) |
| Telegram | botToken, chatId (the bot must be able to post in this chat/channel) |
| Discord | webhookUrl (HTTPS discord.com/api/.../webhooks/... URL) |
| Bluesky | identifier, password (use an app password) |
| Mastodon | instance, accessToken (status/media publishing scopes) |
| Dev.to | apiKey |
For automated local setup, accounts add --input - reads account JSON from stdin:
{"id":"personal","name":"Personal","platform":"mastodon","credentials":{"instance":"https://your.instance","accessToken":"..."}}.
Credentials are saved to the OS keyring. Only account IDs, labels and platform names
are written to ~/.dotshare/accounts.json. Use stdin or an interactive terminal;
avoid putting credentials in command arguments or committed files.
Local storage protection
Linux entries require Secret Service explicitly; there is no automatic kernel-keyring fallback. macOS/Windows use their native OS credential stores. New drafts, custom templates and operation journals always use authenticated AES-256-GCM encryption. The master key is generated randomly and stored in the OS keyring only; a locked, missing or mismatched key fails closed. Restore the original OS key and security.json instead of generating a replacement key for existing ciphertext.
To upgrade an existing installation, stop active publications and run these commands.
npm install -g dotshare-cli
dotshare-agent security status
dotshare-agent security protectFor a source checkout, substitute node bin/dotshare-agent.js for dotshare-agent.
If you switched from a source checkout to the installed package, use
dotshare-agent setup --dry-run --replace to review the new launcher paths.
Then run dotshare-agent setup --replace to replace the existing DotShare entry.
Review the media roots and include --media-root for any directory you want to allow.
protect verifies existing account access, repairs private permissions and converts
existing plaintext records in place. It verifies preserved content without making
plaintext backups. Restart all DotShare MCP clients and VS Code windows afterwards,
then run dotshare-agent doctor --check-accounts. Prior snapshots and backups may
still contain plaintext; the migration does not securely erase old disk pages.
Credential JSON input files must be private (0600 on Unix), regular files owned by the current user; interactive hidden prompts or stdin are preferred. The old CLI relay posting/login path is disabled because it forwarded platform keys or echoed legacy input. Use named local accounts and the direct agent publishing workflow.
See the security model, recovery and limits. Platform keys are not stored in draft/template files. Account IDs/labels remain private metadata rather than encrypted records. Encryption here does not attest the OS provider's own at-rest encryption, protect an unlocked account from same-user malware, or audit the separate DotSuite backend.
Setup and diagnostics
Run these after installing the CLI package (or prefix the commands with
node bin/dotshare-agent.js when using this checkout):
dotshare-agent setup
dotshare-agent doctor
dotshare-agent accounts add personal-mastodon --platform mastodon
dotshare-agent doctor --account personal-mastodonsetup verifies a real MCP handshake, registers the current installation in Codex,
uses absolute Node/server paths, and forwards the Linux desktop session for keyring
access. It preserves other settings, comments and server entries, creates a private
backup when replacing an existing file, and does not rewrite an unchanged entry.
Restart/reconnect Codex after setup; setup cannot prove a running client has reloaded.
Local images remain disabled on a fresh setup until directories are explicitly allowed. Existing allowed directories are retained when setup runs again:
dotshare-agent setup --media-root /absolute/path/to/images
dotshare-agent setup --text-only
dotshare-agent setup --dry-run
dotshare-agent setup --client generic
dotshare-agent setup --config /absolute/path/to/config.toml --json--dry-run checks the server and prints a configuration preview without writing
settings. --client generic prints an mcpServers JSON snippet for manual import;
adapt the wrapper key to your client's format. A different existing DotShare server
is preserved unless --replace is explicitly supplied. Existing inline/dotted-key
DotShare TOML entries must be converted to [mcp_servers.dotshare] before automatic
updates; malformed config files are never overwritten. CODEX_HOME is honored.
doctor checks Node, build output, native keyring binding, client configuration,
image roots, account metadata and a real stdio MCP handshake. It never changes
settings or publishes content. Default checks do not read credentials or contact
platform APIs. Use --check-accounts to read credentials and call identity endpoints,
or --account ID to check just one account. These checks do not create posts,
uploads, OAuth sessions or refresh tokens. Bluesky only resolves the handle and
reports password validity as unverified. A platform HTTP 403 is a warning about read
scopes/API access, not proof that a publishing token is invalid.
Reports distinguish PASS, WARN, and FAIL, with actionable fixes. --json
returns a structured report. Failed checks exit with status 1; warnings alone exit
0. doctor --skip-mcp performs local checks and reports mcpReady: null, never a
successful connection. Generic-client diagnostics verify the server, not the other
client's configuration. Identity verification does not prove posting permissions
or encrypted-at-rest keyring storage.
Manual Codex configuration
From this checkout, replace the paths with absolute paths on your machine:
codex mcp add dotshare -- node /absolute/path/to/DotShare/bin/dotshare-mcp.jsOr merge this into your existing ~/.codex/config.toml:
[mcp_servers.dotshare]
command = "node"
args = ["/absolute/path/to/DotShare/bin/dotshare-mcp.js"]
cwd = "/absolute/path/to/DotShare"
startup_timeout_sec = 20
tool_timeout_sec = 600
# Forward the desktop session for Linux OS keyring access.
env_vars = ["DBUS_SESSION_BUS_ADDRESS", "XDG_RUNTIME_DIR", "DISPLAY", "WAYLAND_DISPLAY"]
[mcp_servers.dotshare.env]
DOTSHARE_MEDIA_ROOTS = "/absolute/path/to/your/images"Restart/reconnect the MCP client after adding the server. Configure another client with the same command/arguments and stdio transport. Official Codex configuration: https://learn.chatgpt.com/docs/extend/mcp?surface=cli.
DOTSHARE_HOME optionally changes the metadata/history directory. Credential keys
in the OS keyring are shared between home directories; account IDs should be unique
if using several directories. DOTSHARE_MEDIA_ROOTS contains allowed image directories
separated by the OS path delimiter (: on Linux/macOS, ; on Windows). Each entry
must be an absolute path. When it is unset or invalid, local images are disabled and
text-only posts still work. Real paths are checked, including symlink targets.
The server never downloads arbitrary social-media URLs or reads credentials from
tool arguments. Diagnostics go to stderr; stdout is reserved for MCP JSON.
Example requests after connecting:
List my DotShare accounts and preview this post for LinkedIn and Mastodon.
Publish the previewed post to work-linkedin and personal-mastodon, with this image.
Turn this release announcement into a thread on my Bluesky account and publish it.
The server exposes list_accounts, preview_post, publish_post, get_operation,
list_history, list_cloud_connections, schedule_post, list_scheduled_posts, and
cancel_scheduled_post, plus create_draft, get_draft, list_drafts,
update_draft, preview_draft, and publish_draft, plus list_templates,
get_template, save_template, delete_template, render_template,
create_draft_from_template, get_draft_variants and set_draft_variant
(23 tools total).
dotshare://guide describes the workflow, and the
release-announcement prompt helps prepare drafts from release notes.
Saved drafts: Draft, Preview and Published
Use the saved-draft workflow when preparing content over several messages or when
publication must match a reviewed preview. The existing preview_post is an
unsaved preview: it does not create a post, draft ID or durable preview snapshot.
For a saved draft, always use publish_draft; copying its text into publish_post
bypasses the draft workflow and is not a substitute for publishing the saved preview.
| Status | Meaning |
|---|---|
Draft | Saved locally; not published. New edits invalidate an earlier preview. |
Preview | A valid preview of this revision is saved; nothing has been published. |
Publishing | A publication was reserved or is in progress. Recover its operation; do not issue a new request ID. |
Published | Publication completed. Per-account delivery results and receipt URLs are returned. |
NeedsReview | Publication failed, was interrupted or may have partially succeeded. Inspect receipts and the destination before further action. |
SavedRemotely | Dev.to accepted the article as a remote draft (published:false); it is not a public article. |
For mixed social/Dev.to targets, publication.deliveries distinguishes public
posts from Dev.to remote drafts. A timeout is never presented as a successful post.
A typical agent conversation:
Save this announcement as a draft for personal-mastodon. Do not publish yet.
Edit that draft to use the English version, then preview it.
Publish that saved preview.
create_draft takes {title?, content} and returns draft.id, draft.revision
and draft.status. Incomplete drafts are allowed, including an empty accountIds
list. update_draft takes draftId, expectedRevision, and a patch of content
fields and/or title. Omitted fields are preserved; set posts or article to
null to remove them, or overrides:null to clear per-account variants. Each edit
increments the revision and resets the state to Draft.
preview_draft requires draftId and expectedRevision. It validates content,
accounts and local images, saves exact thread parts, and returns draft.preview.id.
A failed preview leaves the draft in Draft with validation errors. Repeating preview
creates a new preview ID, even when the content revision is unchanged.
publish_draft requires all four fields:
{
"draftId": "UUID_FROM_CREATE_DRAFT",
"expectedRevision": 2,
"previewId": "UUID_FROM_PREVIEW_DRAFT",
"requestId": "saved-release-2026-11-02-1"
}Before sending anything, publication checks the saved content/plan, current instance limits, account credential bindings and local image fingerprints. A changed image, account, credential or platform plan requires a new preview. Verified local image bytes are copied privately for the attempt, so editing the source during publication cannot change a later upload. Dev.to public image URLs are bound by URL; this does not freeze the remote server's image contents.
Retries use the same request ID, revision and preview ID. Completed/uncertain
operations are replayed from the journal without resending, even after a restart.
get_draft recovers receipt/status updates from that journal if the process stopped
before saving final draft status. A draft with a publication attempt cannot be edited
or submitted under a new ID. A new draft represents a separately authorized action,
not a way to bypass an uncertain result. Short local commit locks and revision checks
prevent concurrent agents from overwriting each other's edits.
Draft commands
For the following commands, draft.json contains
{"title":"MCP release","content":{"accountIds":["personal-mastodon"],"text":"Our release is ready."}}.
The edit file contains {"patch":{"text":"Updated release announcement."}}.
dotshare-agent drafts create --input draft.json
dotshare-agent drafts list --status Draft
dotshare-agent drafts get DRAFT_ID
dotshare-agent drafts update DRAFT_ID --revision 1 --input edit.json
dotshare-agent drafts preview DRAFT_ID --revision 2
dotshare-agent drafts publish DRAFT_ID --revision 2 --preview-id PREVIEW_ID --request-id saved-release-1drafts create --title "Release announcement" creates an empty draft for later
editing. From a source checkout, prefix commands with node bin/dotshare-agent.js.
The MCP client must reconnect after an upgrade to discover new tools;
doctor verifies the complete tool set from a newly started server.
Draft records contain full post bodies, preview snapshots and publication references
under DOTSHARE_HOME/drafts (default ~/.dotshare/drafts). These JSON files are
encrypted with AES-256-GCM; directory/file permissions are 0700/0600. The random master key stays in the required OS keyring, never beside these files. Credentials are kept
in the OS keyring; drafts contain credential fingerprints, not the keys themselves.
list_drafts returns summaries without post bodies. Draft content is limited to
1 MiB, and preview plans to 2 MiB. The VS Code Publishing Library uses this same
store through the configured MCP installation. Earlier platform-composer drafts
remain separate; no automatic migration is performed.
Reusable templates and account versions
Templates are local content recipes, not scheduled jobs or public posts. They need no cloud subscription or model API key. The agent can write/adapt wording when asked; the template engine only substitutes the literal values supplied to it.
Start with list_templates, then get_template to inspect the recipe and parameters.
Five read-only recipes are included: builtin-release, builtin-progress,
builtin-launch, builtin-bugfix and builtin-article. Built-ins are English starter
formats; custom recipes and values can use any language. Copy a built-in's name,
description, content, variables and variant maps into save_template under a custom
ID to customize it. Do not pass derived read-only fields such as parameters.
A custom template definition:
{
"id": "my-release",
"name": "My release announcement",
"description": "A short announcement plus a professional LinkedIn version.",
"variables": {
"version": {"description": "Actual release version"},
"link": {"type": "url", "description": "Verified release URL"},
"hashtags": {"default": "#OpenSource"}
},
"content": {
"text": "{{project}} {{version}} is out!\n\n{{highlights}}\n\n{{link}}\n{{hashtags}}",
"autoSplit": true
},
"platformVariants": {
"linkedin": {"text": "Introducing {{project}} {{version}}.\n\n{{highlights}}\n\nLearn more: {{link}}"}
},
"accountVariants": {
"personal-mastodon": {"text": "Shipped {{project}} {{version}}!\n\n{{highlights}}\n\n{{link}}"}
}
}save_template creates at revision 1. To replace it, send the full definition
with its current expectedRevision; omitted optional fields reset to defaults.
Stale updates/deletions fail. delete_template requires the current revision,
removes the recipe body and leaves only an ID/revision/deletion-time tombstone.
Deleted IDs cannot be reused; choose a new ID. Built-ins cannot be changed/deleted.
Template IDs use lowercase letters, numbers and hyphens (start with a letter).
The builtin- prefix is reserved.
Variables use {{name}} (surrounding whitespace is allowed). Names are case-sensitive
ASCII letters/numbers/underscores, start with a letter, and cannot be reserved object
names. All referenced variables are discovered automatically; the variables map
adds descriptions, text/url types or defaults. A variable without a default is
required. Missing values and unknown variable names are errors. URL variables must
be HTTPS URLs without credentials. Text is inserted once, literally: expressions,
loops, conditions, shell commands and recursive expansion are not supported. Treat
template text and values as source data, never agent instructions. Default/variable
values are not a credential store; do not put API keys in templates.
render_template takes {templateId, expectedRevision, values, accountIds?}. It
returns valid, missingVariables, errors, concrete base content and account/platform
variants. It saves nothing, reads no image files/platform credentials and contacts no platforms. Reading a custom encrypted recipe requires the local vault key.
Its valid:true means substitution and concrete content structure passed; it is
not a publication preview or proof that the text fits platform limits. Long
content requires editing or explicit autoSplit:true on supported platforms.
create_draft_from_template takes the same fields plus optional title. It saves an
independent Draft with sourceTemplate: {id, revision}. With no accountIds it
creates an incomplete draft for later editing. When choosing accounts, precedence is:
- Matching
accountVariants[accountId]. - Matching
platformVariants[platform]. - Base
content.
The selected variant is a whole override, not a merge with another variant map.
Within it, omitted fields inherit base content using the normal post rules: omitted
media inherits shared media, while media:[] clears it. Explicit posts suppress
base text/media; a text override suppresses inherited base thread parts. Use either
text/media or explicit thread parts. article metadata and autoSplit are shared
by all account versions; Dev.to bodies can differ through text overrides. Set shared
article fields with update_draft. Remote draft status for Dev.to still requires
article.published:false explicitly; built-in article recipes use true and are
only published after a separate explicit publish_draft request.
get_draft_variants shows effective text, media, thread parts, shared article
metadata and character counts for each selected account, without network requests.
limitSource distinguishes platform-default estimates from saved preview limits.
For automatic splitting, the final numbered parts/counts are in preview_draft.
set_draft_variant takes {draftId, expectedRevision, accountId, variant} and
replaces one account override, preserving every other account. variant:null
restores inheritance from the draft's current base content. It does not reapply an
old platform recipe. It increments the revision and invalidates the saved preview.
It cannot edit a draft that already has a publication attempt.
Changing/deleting a template never changes earlier drafts. Each draft holds its own
filled content, and its publishing snapshot/retry safeguards remain in effect.
Template files live under DOTSHARE_HOME/templates with 0700/0600 permissions;
like draft bodies and operation records, they are encrypted with an OS-keyring master key. Stored templates and expanded content
are bounded to 1 MiB; templates allow at most 100 distinct variables. No VS Code
template/draft migration is performed. The Publishing Library connects directly to
this same store; see the visual workflow.
Example agent requests:
Show available templates and the release template's required variables.
Use the release template with these verified release notes and this URL. Save a draft for personal-mastodon and work-linkedin. Do not publish.
Show both account versions. Make LinkedIn more formal and keep Mastodon short. Preview the updated draft without publishing.
Publish that saved preview.
CLI equivalents (recipe.json contains the definition above; values.json contains
{"values":{"project":"DotShare","version":"vNext","highlights":"Supplied release notes","link":"https://example.com/release"},"accountIds":["personal-mastodon"]};
variant.json contains {"text":"My revised Mastodon text"}):
dotshare-agent templates list
dotshare-agent templates get builtin-release
dotshare-agent templates save --input recipe.json
dotshare-agent templates render my-release --revision 1 --input values.json
dotshare-agent templates use my-release --revision 1 --input values.json
dotshare-agent drafts variants DRAFT_ID
dotshare-agent drafts set-variant DRAFT_ID personal-mastodon --revision 1 --input variant.json
dotshare-agent drafts preview DRAFT_ID --revision 2
dotshare-agent drafts publish DRAFT_ID --revision 2 --preview-id PREVIEW_ID --request-id my-release-1
dotshare-agent templates delete my-release --revision 1Use JSON null as the set-variant input to reset an account to base inheritance.
All commands taking --input also accept - for stdin. From this checkout prefix
commands with node bin/dotshare-agent.js.
Content and preview
Use the same post structure for preview_post and publish_post. Publishing also
requires a stable requestId identifying one user-authorized action:
{
"requestId": "dotshare-release-2026-11-02-1",
"accountIds": ["personal-mastodon", "work-linkedin"],
"text": "DotShare now works with AI agents!",
"media": ["/absolute/path/to/your/images/launch.png"],
"autoSplit": true,
"overrides": {
"work-linkedin": { "text": "We shipped agent-driven publishing in DotShare." }
}
}Omit requestId when calling preview_post (schemas reject unrelated fields).
The CLI accepts the same JSON through preview --input file.json or
publish --input file.json; --input - reads stdin. Preview returns exact planned
text, image paths, character counts, limits and validation errors, and publishes
nothing. Mastodon instance limits are fetched when that account is previewed.
posts: [{"text":"First"},{"text":"Second","media":["/path/image.png"]}]
creates an explicit thread on X, Bluesky or Mastodon. Use either posts or
text/media. autoSplit splits long text into numbered thread parts on those three
platforms, with common images attached to the first part. overrides can specify
different text, images or explicit thread parts per account. Unsupported threads,
empty posts, missing images and oversized content fail preflight before any account
is published to. Character counts for combined emoji on X/Bluesky are conservative.
| Agent media support | Limits |
|---|---|
| Local images | PNG/JPEG; WebP also on Bluesky, Mastodon and Discord |
| Size | 1,000,000 bytes/image on Bluesky; 8 MiB/image otherwise, subject to platform limits |
| Images/post | X/Bluesky/Mastodon up to 4 (Mastodon also checks instance limits); LinkedIn 1; Telegram/Discord 10 |
| Telegram | Caption limited to 1,024 characters; multiple images are sent as separate messages, caption on first |
| Dev.to | Public HTTPS image URLs; local files are rejected |
| Video/GIF | Not supported by this initial agent interface |
Dev.to requires article: {"title":"Release notes","tags":["opensource"],"published":true}.
Optional article fields include canonicalUrl, coverImage, description, and
series. Set published:false to create a Dev.to draft intentionally.
Retry and recovery
Each publish/schedule/cancel operation is claimed atomically and retained in
~/.dotshare/operations with private file permissions. The journal records receipts
as each thread part is published. It stores a request fingerprint and result metadata,
not platform credentials or full post bodies.
- Repeat the same requestId and payload to retrieve the existing result without sending another publication request, including after a reconnect/process restart.
- Reusing an ID for different content fails. IDs have no automatic expiry.
- A timeout can occur after a platform accepted a post.
needs_review,uncertain, and stalerunningoperations require inspection of the destination, not an automatic retry with a new ID. Already published parts are not rolled back. - To intentionally publish the same content again, provide a new user-authorized ID.
operation <requestId>andhistoryrecover local results. Credentials must be available for all target accounts before the first publication starts.
This prevents application retries from resending a claimed action; it does not provide a distributed exactly-once guarantee across external platform APIs or after deleting the local journal. Back up the journal if migrating machines.
Cloud scheduling
node bin/dotshare-agent.js cloud login
node bin/dotshare-agent.js cloud connections
node bin/dotshare-agent.js cloud schedule --input schedule.json
node bin/dotshare-agent.js cloud list
node bin/dotshare-agent.js cloud cancel SCHEDULED_POST_ID --request-id cancel-post-1Connect the desired platform accounts in the DotSuite dashboard first. Local named accounts and cloud connections are separate: cloud requests select platforms, using the accounts already connected on the server. No local platform keys are synced. The DotSuite session token is entered in your terminal and stored in the OS keyring. Backend plan limits, permissions and available connections still apply.
{
"requestId": "scheduled-launch-1",
"platforms": ["mastodon", "x"],
"text": "Our release is live!",
"mediaUrls": ["https://your-public-cdn.example/launch.png"],
"scheduledAt": "2026-12-01T10:00:00+02:00"
}Scheduling requires a timezone and at least one minute of lead time. It supports one
post with public HTTPS image URLs, without local file uploads, threads or account
overrides. Discord cloud scheduling is unavailable. DOTSUITE_API_URL optionally
selects a compatible HTTPS backend. It cannot contain credentials/query parameters.
Build and validate
npm run lint
npx tsc --noEmit
npm run test:all -- --runInBand
npm run test:agent:stdio
npm run package:cliThe packaging command builds production bundles and stages the CLI in a temporary
directory, leaving the VS Code package.json unchanged. The resulting npm tarball
is saved under artifacts/; it can be installed locally with npm install -g PATH.tgz.
Unit/integration tests use mocked platform APIs. The subprocess smoke test checks
the real MCP transport and CLI binaries without publishing anything publicly.