dunsocial 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -3
- package/dist/index.js +134 -74
- package/package.json +1 -1
- package/skills/dunsocial/SKILL.md +44 -5
package/README.md
CHANGED
|
@@ -18,6 +18,8 @@ dun update
|
|
|
18
18
|
# or: dun update --check
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
On a human terminal, a global install (npm / bun / pnpm / yarn) auto-upgrades after a successful command when a newer non-major version is on npm. Agents, CI, `--json`, and `npx` are never auto-updated. Disable with `DUN_UPDATE=0` or `--no-update`.
|
|
22
|
+
|
|
21
23
|
---
|
|
22
24
|
|
|
23
25
|
## Get started
|
|
@@ -71,9 +73,9 @@ dun posts publish \
|
|
|
71
73
|
|
|
72
74
|
---
|
|
73
75
|
|
|
74
|
-
## LinkedIn
|
|
76
|
+
## LinkedIn video
|
|
75
77
|
|
|
76
|
-
Personal LinkedIn
|
|
78
|
+
Personal LinkedIn and Company Pages (`linkedin-business`) support **images** (up to 20) **or exactly one video** (MP4/MOV, ≤512MB). Do not mix video and images. No `--meta` needed.
|
|
77
79
|
|
|
78
80
|
```bash
|
|
79
81
|
# 1. Find your personal LinkedIn account id
|
|
@@ -93,12 +95,29 @@ dun posts publish \
|
|
|
93
95
|
|
|
94
96
|
---
|
|
95
97
|
|
|
98
|
+
## YouTube Shorts only
|
|
99
|
+
|
|
100
|
+
YouTube publishes **Shorts**, not regular videos. Attach exactly one vertical or square clip (3 minutes or less) with `--media`. The uploaded asset must include width, height, and duration or publish is rejected.
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
dun accounts list --platform youtube --json
|
|
104
|
+
dun media upload ./short.mp4 --json
|
|
105
|
+
dun posts publish \
|
|
106
|
+
--accounts YOUTUBE_ACCOUNT_ID \
|
|
107
|
+
--text "Title"$'\n\n'"Description" \
|
|
108
|
+
--media ASSET_ID \
|
|
109
|
+
--wait \
|
|
110
|
+
--json
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
96
115
|
## Reddit
|
|
97
116
|
|
|
98
117
|
Reddit needs platform options via `--meta`. Many subreddits also require a flair **template id**.
|
|
99
118
|
|
|
100
119
|
```bash
|
|
101
|
-
# 1. Upload media first (
|
|
120
|
+
# 1. Upload media first (gallery accepts video up to 16GB)
|
|
102
121
|
dun media upload ./demo.mp4 --json
|
|
103
122
|
|
|
104
123
|
# 2. List flairs for the subreddit (use id, not just the label text)
|
package/dist/index.js
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{readFileSync as
|
|
3
|
-
`,"utf8");try{await
|
|
2
|
+
import{readFileSync as ft}from"node:fs";import{mkdir as gt,readFile as wt,writeFile as ht,chmod as kt,unlink as bt}from"node:fs/promises";import{dirname as yt,join as X}from"node:path";import{homedir as It}from"node:os";var c={OK:0,USAGE:1,AUTH:2,FORBIDDEN:3,VALIDATION:4,NOT_FOUND:5,RATE_LIMITED:6,NETWORK:7,PAYMENT:8};class d extends Error{code;details;constructor(e,n=c.USAGE,t){super(e);this.name="CliError",this.code=n,this.details=t}}function ke(e){if(e===401)return c.AUTH;if(e===402)return c.PAYMENT;if(e===403)return c.FORBIDDEN;if(e===404)return c.NOT_FOUND;if(e===429)return c.RATE_LIMITED;if(e>=400&&e<500)return c.VALIDATION;return c.NETWORK}var At="https://api.dunsocial.com",be="0.15.0";function St(){if(!be.includes("@@"))return be;try{let e=JSON.parse(ft(X(import.meta.dir,"..","package.json"),"utf8"));if(typeof e.version==="string"&&e.version.length>0)return e.version}catch{}return"0.0.0"}var T=St();function W(){if(process.env.DUN_CONFIG)return process.env.DUN_CONFIG;let e=process.env.XDG_CONFIG_HOME,n=e?e:X(It(),".config");return X(n,"dunsocial","config.json")}function D(){return W()}async function Z(){let e=W();try{let n=await wt(e,"utf8");return JSON.parse(n)}catch(n){if(n.code==="ENOENT")return{};throw new d(`Failed to read config at ${e}: ${n.message}`,c.USAGE)}}async function P(e){let n=W();await gt(yt(n),{recursive:!0});let t={...e,updatedAt:new Date().toISOString()};await ht(n,`${JSON.stringify(t,null,2)}
|
|
3
|
+
`,"utf8");try{await kt(n,384)}catch{}}async function ye(){let e=W();try{await bt(e)}catch(n){if(n.code!=="ENOENT")throw new d(`Failed to clear config: ${n.message}`,c.USAGE)}}async function Ie(e){let n=await Z(),t=(e.apiUrl||process.env.DUN_API_URL||n.apiUrl||At).replace(/\/$/,""),i=e.token||process.env.DUN_TOKEN||n.token,a=e.workspace||process.env.DUN_WORKSPACE_ID||n.defaultWorkspaceId||void 0,r=process.env.DUN_JSON==="0",s=!process.stdout.isTTY,o=r?!1:e.json===!0||process.env.DUN_JSON==="1"||process.env.CI==="true"||process.env.CI==="1"||s;return{apiUrl:t,token:i,workspaceId:a,json:o,debug:e.debug===!0||process.env.DUN_DEBUG==="1",quiet:e.quiet===!0,yes:e.yes===!0||process.env.CI==="true"||process.env.CI==="1",config:n}}function y(e){if(!e.token)throw new d("Not authenticated. Run `dun auth login` (or `dun auth login --token <token>` / set DUN_TOKEN).",c.AUTH);return e.token}function I(e){if(!e.workspaceId)throw new d("No workspace selected. Run `dun workspace use <id|slug>` or pass --workspace / DUN_WORKSPACE_ID.",c.VALIDATION);return e.workspaceId}function Q(e){let n={},t=[],i=0,a=!0;while(i<e.length){let u=e[i];if(a&&u==="--"){a=!1,i+=1;continue}if(a&&u.startsWith("--")){let m=u.indexOf("=");if(m!==-1){n[u.slice(2,m)]=u.slice(m+1),i+=1;continue}let g=u.slice(2),w=e[i+1];if(w!==void 0&&!w.startsWith("-"))n[g]=w,i+=2;else n[g]=!0,i+=1;continue}if(a&&u.startsWith("-")&&u.length>1&&u!=="-"){let m=u.slice(1);if(m.length>1&&!m.includes("=")){for(let k of m)n[k]=!0;i+=1;continue}let g=m,w=e[i+1];if(w!==void 0&&!w.startsWith("-"))n[g]=w,i+=2;else n[g]=!0,i+=1;continue}t.push(u),i+=1}let r=t[0],s,o,l=1;if(r==="memory"&&(t[1]==="collections"||t[1]==="collection"))o="collections",s=t[2],l=s?3:2;else s=t[1],l=s?2:1;return{group:r,action:s,nest:o,flags:n,positionals:t.slice(l)}}function f(e,...n){for(let t of n){let i=e[t];if(typeof i==="string"&&i.length>0)return i}return}function h(e,...n){for(let t of n){let i=e[t];if(i===!0)return!0;if(typeof i==="string"){let a=i.toLowerCase();if(a==="1"||a==="true"||a==="yes")return!0}}return!1}function U(e,...n){let t=f(e,...n);if(t===void 0)return;let i=Number(t);if(!Number.isFinite(i))throw Error(`Invalid number for --${n[0]}: ${t}`);return i}function E(e,...n){let t=f(e,...n);if(t===void 0)return;return t.split(",").map((i)=>i.trim()).filter(Boolean)}function ee(e){return h(e,"help","h")}function te(e){return h(e,"version","v","V")}function p(e,n){if(n.quiet&&!n.json)return;if(n.json||!process.stdout.isTTY){process.stdout.write(`${JSON.stringify({ok:!0,data:e},null,2)}
|
|
4
4
|
`);return}if(e===void 0||e===null){process.stdout.write(`ok
|
|
5
5
|
`);return}if(typeof e==="string"){process.stdout.write(`${e}
|
|
6
|
-
`);return}process.stdout.write(`${
|
|
7
|
-
`)}function
|
|
6
|
+
`);return}process.stdout.write(`${Et(e,n.emptyMessage)}
|
|
7
|
+
`)}function Se(e,n){let{message:t,code:i,details:a}=Ut(e);if(n.json||!process.stdout.isTTY)process.stdout.write(`${JSON.stringify({ok:!1,error:{code:Ct(i),message:t,details:a??null}},null,2)}
|
|
8
8
|
`);else{process.stderr.write(`error: ${t}
|
|
9
|
-
`);let
|
|
10
|
-
`);else if(
|
|
11
|
-
`)}return
|
|
12
|
-
`)}if(e&&typeof e==="object"){let t=e;if(Array.isArray(t.items)){if(t.items.length===0&&n)return n;let
|
|
13
|
-
${
|
|
14
|
-
`)}return String(e)}function
|
|
15
|
-
`);return`${
|
|
16
|
-
${
|
|
9
|
+
`);let r=Tt(a);if(i===c.PAYMENT&&r)process.stderr.write(`Subscribe: ${r}
|
|
10
|
+
`);else if(a!==void 0&&process.env.DUN_DEBUG==="1")process.stderr.write(`${JSON.stringify(a,null,2)}
|
|
11
|
+
`)}return i}function Ct(e){switch(e){case c.AUTH:return"auth";case c.FORBIDDEN:return"forbidden";case c.VALIDATION:return"validation";case c.NOT_FOUND:return"not_found";case c.RATE_LIMITED:return"rate_limited";case c.NETWORK:return"network";case c.PAYMENT:return"payment_required";default:return"usage"}}function Tt(e){if(!e||typeof e!=="object")return;let n=e;if(typeof n.billingUrl==="string")return n.billingUrl;let t=n.body;if(t&&typeof t==="object"&&typeof t.billingUrl==="string")return t.billingUrl;return}function Ut(e){if(e instanceof d)return{message:e.message,code:e.code,details:e.details};if(e instanceof Error)return{message:e.message,code:c.USAGE};return{message:String(e),code:c.USAGE}}function Et(e,n){if(Array.isArray(e)){if(e.length===0)return n??"(empty)";if(e.every((t)=>t&&typeof t==="object"))return Ae(e,n);return e.map((t)=>`- ${ne(t)}`).join(`
|
|
12
|
+
`)}if(e&&typeof e==="object"){let t=e;if(Array.isArray(t.items)){if(t.items.length===0&&n)return n;let i=typeof t.total==="number"?`total=${t.total} showing=${t.items.length}`:void 0,a=Ae(t.items,n);return i?`${i}
|
|
13
|
+
${a}`:a}return Object.entries(t).map(([i,a])=>`${i}: ${ne(a)}`).join(`
|
|
14
|
+
`)}return String(e)}function Ae(e,n){if(e.length===0)return n??"(empty)";let t=["id","name","slug","status","providerName","username","displayName","role","scheduledAt","content","text","originalFilename","score","collectionId"],i=new Set;for(let u of e)for(let m of Object.keys(u))i.add(m);let a=[...t.filter((u)=>i.has(u)),...[...i].filter((u)=>!t.includes(u)).slice(0,6)].slice(0,8),r=e.map((u)=>a.map((m)=>Nt(ne(u[m]),m==="content"||m==="text"?48:28))),s=a.map((u,m)=>Math.max(u.length,...r.map((g)=>g[m]?.length??0))),o=a.map((u,m)=>u.padEnd(s[m])).join(" "),l=r.map((u)=>u.map((m,g)=>m.padEnd(s[g])).join(" ")).join(`
|
|
15
|
+
`);return`${o}
|
|
16
|
+
${l}`}function ne(e){if(e===null||e===void 0)return"";if(typeof e==="string")return e.replace(/\s+/g," ").trim();if(typeof e==="number"||typeof e==="boolean")return String(e);if(e instanceof Date)return e.toISOString();if(typeof e==="object"){let n=e;if(typeof n.id==="string"&&typeof n.name==="string")return`${n.name} (${n.id})`;if(typeof n.status==="string")return n.status;return JSON.stringify(e)}return String(e)}function Nt(e,n){if(e.length<=n)return e;return`${e.slice(0,Math.max(0,n-1))}...`}function R(e,n,t){if(!e)return;if(t!==void 0){process.stderr.write(`[debug] ${n} ${JSON.stringify(t)}
|
|
17
17
|
`);return}process.stderr.write(`[debug] ${n}
|
|
18
|
-
`)}function
|
|
19
|
-
${
|
|
18
|
+
`)}function C(e,n){let t=`${e} ${n}`,i="-".repeat(Math.max(12,t.length+4));return`${t}
|
|
19
|
+
${i}`}var re=`dun - schedule & publish social posts
|
|
20
20
|
|
|
21
21
|
Usage:
|
|
22
22
|
dun <command> [flags]
|
|
23
23
|
|
|
24
|
-
${
|
|
24
|
+
${C("\uD83D\uDD10","Auth")}
|
|
25
25
|
dun auth login # device-code browser flow
|
|
26
26
|
dun auth login --token <token> # CI / headless
|
|
27
27
|
dun auth login --no-browser
|
|
@@ -29,26 +29,36 @@ ${S("\uD83D\uDD10","Auth")}
|
|
|
29
29
|
dun auth whoami
|
|
30
30
|
dun auth status
|
|
31
31
|
|
|
32
|
-
${
|
|
32
|
+
${C("\uD83D\uDCC1","Workspaces")}
|
|
33
33
|
dun workspace list
|
|
34
34
|
dun workspace use <id|slug>
|
|
35
35
|
dun workspace current
|
|
36
36
|
|
|
37
|
-
${
|
|
37
|
+
${C("\uD83D\uDD17","Accounts")}
|
|
38
38
|
dun accounts list [--platform x|reddit|...]
|
|
39
39
|
|
|
40
|
-
${
|
|
40
|
+
${C("\uD83D\uDFE0","Reddit")}
|
|
41
41
|
dun reddit flairs <subreddit> # template id + text + textEditable
|
|
42
42
|
dun reddit requirements <subreddit> # live post rules (flair required, ...)
|
|
43
43
|
# alias: dun accounts reddit flairs <subreddit>
|
|
44
44
|
|
|
45
|
-
${
|
|
45
|
+
${C("\uD83D\uDCCC","Pinterest")}
|
|
46
|
+
dun pinterest boards # live boards for the connected account
|
|
47
|
+
dun pinterest create-board --name "..." [--private]
|
|
48
|
+
# alias: dun accounts pinterest boards
|
|
49
|
+
|
|
50
|
+
${C("\uD83D\uDCAC","Discord")}
|
|
51
|
+
dun discord channels [--account <id>] # text and announcement channels
|
|
52
|
+
# alias: dun accounts discord channels
|
|
53
|
+
# then: --meta '{"discord":{"channelId":"..."}}'
|
|
54
|
+
|
|
55
|
+
${C("\uD83D\uDCDD","Posts")}
|
|
46
56
|
dun posts list [--status scheduled|published|failed|draft|cancelled]
|
|
47
57
|
dun posts get <id>
|
|
48
58
|
dun posts validate --accounts <id> --text "..." [--media id] [--meta '{...}']
|
|
49
59
|
dun posts schedule --text "..." --accounts <id,id> (--at ISO | --in 2h) [--media id,id] [--meta '{...}']
|
|
50
60
|
dun posts publish --text "..." --accounts <id,id> [--media id,id] [--meta '{...}'] [--natural] [--wait]
|
|
51
|
-
dun posts reschedule <id> --at ISO|--in 2h [--text "..."]
|
|
61
|
+
dun posts reschedule <id> --at ISO|--in 2h [--text "..."] [--media id] [--meta '{...}']
|
|
52
62
|
dun posts cancel <id>
|
|
53
63
|
dun posts delete <id> [--yes]
|
|
54
64
|
dun posts x-cap
|
|
@@ -59,24 +69,24 @@ ${S("\uD83D\uDCDD","Posts")}
|
|
|
59
69
|
# Reddit --meta example (video):
|
|
60
70
|
# --meta '{"reddit":{"subreddit":"tamilyapping","kind":"video","title":"...","flairId":"..."}}'
|
|
61
71
|
# kinds: self | link | image | video
|
|
62
|
-
#
|
|
63
|
-
#
|
|
72
|
+
# LinkedIn video (no --meta): upload one mp4/mov, then --media ASSET on a LinkedIn or Company Page account
|
|
73
|
+
# Do not mix video + images on LinkedIn.
|
|
64
74
|
# publish queues async work; without --wait, status scheduled means queued
|
|
65
75
|
|
|
66
|
-
${
|
|
76
|
+
${C("\uD83D\uDCC4","Drafts")}
|
|
67
77
|
dun drafts list
|
|
68
78
|
dun drafts get <id>
|
|
69
79
|
dun drafts create --text "..." [--platforms x,linkedin] [--accounts id,id]
|
|
70
80
|
dun drafts update <id> --text "..."
|
|
71
81
|
dun drafts delete <id> [--yes]
|
|
72
82
|
|
|
73
|
-
${
|
|
83
|
+
${C("\uD83D\uDDBC️","Media")}
|
|
74
84
|
dun media list
|
|
75
85
|
dun media get <id>
|
|
76
|
-
dun media upload <file> [--alt "..."] # local preflight: images <=8MB, video <=
|
|
86
|
+
dun media upload <file> [--alt "..."] # local preflight: images <=8MB, video <=16GB
|
|
77
87
|
dun media delete <id> [--yes]
|
|
78
88
|
|
|
79
|
-
${
|
|
89
|
+
${C("\uD83E\uDDE0","Memory")}
|
|
80
90
|
dun memory collections list
|
|
81
91
|
dun memory collections create --name "..." [--color blue] [--private]
|
|
82
92
|
dun memory collections get <id>
|
|
@@ -87,7 +97,7 @@ ${S("\uD83E\uDDE0","Memory")}
|
|
|
87
97
|
dun memory search --prompt "..." --collections <id,id> [--top-k 5]
|
|
88
98
|
dun memory delete <id> --collection <id> [--yes]
|
|
89
99
|
|
|
90
|
-
${
|
|
100
|
+
${C("\uD83C\uDF99️","Personalization")}
|
|
91
101
|
# workspace AI voice
|
|
92
102
|
dun personalization get
|
|
93
103
|
dun personalization set --description "..."
|
|
@@ -95,21 +105,24 @@ ${S("\uD83C\uDF99️","Personalization")}
|
|
|
95
105
|
dun personalization set --clear
|
|
96
106
|
# requires PAT scope workspace:write for set (owner/admin)
|
|
97
107
|
|
|
98
|
-
${
|
|
108
|
+
${C("\uD83E\uDDE9","Agent skills")}
|
|
99
109
|
dun skill install <agent|all>
|
|
100
110
|
dun skill list
|
|
101
111
|
dun skill path
|
|
102
112
|
# agents: claude codex cursor pi opencode agents copilot windsurf
|
|
103
113
|
# goose gemini amp grok continue antigravity
|
|
104
114
|
|
|
105
|
-
${
|
|
115
|
+
${C("⬆️","Update")}
|
|
106
116
|
dun update # upgrade to latest from npm
|
|
107
117
|
dun update --check # report only
|
|
108
118
|
dun update --force # reinstall latest even if current
|
|
109
119
|
# aliases: upgrade, self-update
|
|
110
120
|
# detects npm / bun / pnpm / yarn; prints command for npx
|
|
121
|
+
# TTY + global install: auto-upgrades after a successful command (not agents/CI/npx)
|
|
122
|
+
# disable: --no-update or DUN_UPDATE=0
|
|
123
|
+
# major bumps print: dun update --yes
|
|
111
124
|
|
|
112
|
-
${
|
|
125
|
+
${C("⚙️","Global flags")}
|
|
113
126
|
--json Machine-readable output (default when non-TTY / CI)
|
|
114
127
|
--workspace <id> Override workspace for this command
|
|
115
128
|
--api-url <url> Override API base (default https://api.dunsocial.com)
|
|
@@ -117,17 +130,18 @@ ${S("⚙️","Global flags")}
|
|
|
117
130
|
--debug Log HTTP method/path/status to stderr
|
|
118
131
|
--quiet Suppress human success output
|
|
119
132
|
--yes Skip destructive confirmations
|
|
133
|
+
--no-update Skip silent auto-update for this command
|
|
120
134
|
--help, -h Show help
|
|
121
135
|
--version, -v Show version
|
|
122
136
|
|
|
123
|
-
${
|
|
124
|
-
DUN_TOKEN DUN_WORKSPACE_ID DUN_API_URL DUN_CONFIG DUN_JSON=1 DUN_DEBUG=1
|
|
137
|
+
${C("\uD83C\uDF31","Env")}
|
|
138
|
+
DUN_TOKEN DUN_WORKSPACE_ID DUN_API_URL DUN_CONFIG DUN_JSON=1 DUN_DEBUG=1 DUN_UPDATE=0
|
|
125
139
|
|
|
126
|
-
${
|
|
140
|
+
${C("\uD83D\uDEAA","Exit codes")}
|
|
127
141
|
0 ok | 1 usage | 2 auth | 3 forbidden | 4 validation | 5 not found | 6 rate limit | 7 network | 8 payment
|
|
128
142
|
|
|
129
143
|
Docs: https://dunsocial.com/docs/cli
|
|
130
|
-
`;function
|
|
144
|
+
`;function ie(e){return{auth:`dun auth
|
|
131
145
|
|
|
132
146
|
login [--token] Device-code browser login (default) or --token for CI
|
|
133
147
|
logout Clear local config
|
|
@@ -140,6 +154,33 @@ Docs: https://dunsocial.com/docs/cli
|
|
|
140
154
|
current Show default workspace
|
|
141
155
|
`,workspaces:"Alias of workspace",accounts:`dun accounts list [--platform <name>]
|
|
142
156
|
dun accounts reddit flairs <subreddit> # alias of dun reddit flairs
|
|
157
|
+
dun accounts pinterest boards # alias of dun pinterest boards
|
|
158
|
+
dun accounts discord channels # alias of dun discord channels
|
|
159
|
+
`,discord:`dun discord
|
|
160
|
+
|
|
161
|
+
channels [--account <id>] List text and announcement channels for the connected Discord server
|
|
162
|
+
|
|
163
|
+
Output fields: id, name, type (text | announcement)
|
|
164
|
+
Use channel id in
|
|
165
|
+
dun posts publish --meta '{"discord":{"channelId":"..."}}'
|
|
166
|
+
|
|
167
|
+
Example:
|
|
168
|
+
dun discord channels --json
|
|
169
|
+
dun discord channels --account <id> --json
|
|
170
|
+
`,pinterest:`dun pinterest
|
|
171
|
+
|
|
172
|
+
boards List boards for the connected Pinterest account
|
|
173
|
+
create-board --name "..." Create a public board on Pinterest
|
|
174
|
+
create-board --name "..." --private
|
|
175
|
+
Create a secret board (not on the public profile)
|
|
176
|
+
|
|
177
|
+
Output fields for boards: id, name, privacy, url
|
|
178
|
+
create-board returns the live Pinterest board id. Use that boardId in
|
|
179
|
+
dun posts publish --meta '{"pinterest":{"boardId":"...","title":"...","description":"..."}}'
|
|
180
|
+
|
|
181
|
+
Example:
|
|
182
|
+
dun pinterest boards --json
|
|
183
|
+
dun pinterest create-board --name "Landing page" --json
|
|
143
184
|
`,reddit:`dun reddit
|
|
144
185
|
|
|
145
186
|
flairs <subreddit> List flair template ids for the connected Reddit account
|
|
@@ -156,7 +197,7 @@ Example:
|
|
|
156
197
|
validate --accounts --text [--media] [--meta] Dry-run schedule checks (no writes)
|
|
157
198
|
schedule --text --accounts (--at|--in) [--media] [--meta]
|
|
158
199
|
publish --text --accounts [--media] [--meta] [--natural] [--wait] [--wait-timeout 120]
|
|
159
|
-
reschedule <id> (--at|--in) [--text]
|
|
200
|
+
reschedule <id> (--at|--in) [--text] [--media] [--meta]
|
|
160
201
|
cancel <id>
|
|
161
202
|
delete <id> [--yes]
|
|
162
203
|
x-cap
|
|
@@ -169,18 +210,26 @@ Platform metadata (--meta / --metadata / --meta-json):
|
|
|
169
210
|
Reddit (required when publishing to a Reddit account):
|
|
170
211
|
{"reddit":{"subreddit":"NAME","kind":"self|link|image|video","title":"...","flairId":"..."}}
|
|
171
212
|
Optional reddit fields: body, url (link), flairText (only if textEditable), posterMediaAssetId, nsfw, spoiler
|
|
213
|
+
Discord (required when publishing to a Discord account):
|
|
214
|
+
{"discord":{"channelId":"SNOWFLAKE"}}
|
|
215
|
+
Optional discord fields: channelName
|
|
216
|
+
Discover channel ids with: dun discord channels --json
|
|
172
217
|
|
|
173
218
|
Reddit happy path:
|
|
174
|
-
1. dun media upload ./video.mp4 --json # rejects >
|
|
219
|
+
1. dun media upload ./video.mp4 --json # rejects >16GB locally
|
|
175
220
|
2. dun reddit flairs SUB --json # pick flairId
|
|
176
221
|
3. dun posts validate --accounts ID --media ASSET --meta '...' --json
|
|
177
222
|
4. dun posts publish --accounts ID --media ASSET --meta '...' --wait --json
|
|
178
223
|
|
|
179
|
-
|
|
180
|
-
1. dun accounts list --platform linkedin --json #
|
|
181
|
-
2. dun media upload ./clip.mp4 --json # mp4 or mov; <=512MB
|
|
224
|
+
LinkedIn video (no --meta):
|
|
225
|
+
1. dun accounts list --platform linkedin --json # or linkedin-business for a Company Page
|
|
226
|
+
2. dun media upload ./clip.mp4 --json # mp4 or mov; <=16GB gallery, LinkedIn still ~512MB
|
|
182
227
|
3. dun posts publish --accounts ID --text "..." --media ASSET --wait --json
|
|
183
|
-
Rules: exactly one video OR images (not mixed). Company Pages
|
|
228
|
+
Rules: exactly one video OR images (not mixed). Personal profiles and Company Pages both accept video.
|
|
229
|
+
|
|
230
|
+
YouTube Shorts only:
|
|
231
|
+
Vertical or square video, 3 minutes or less. The uploaded asset must include width, height, and duration
|
|
232
|
+
or publish is rejected (landscape/unknown dimensions will not post as a regular YouTube video).
|
|
184
233
|
|
|
185
234
|
Notes:
|
|
186
235
|
- publish without --wait returns status scheduled (queued). Poll get or use --wait.
|
|
@@ -192,7 +241,7 @@ Notes:
|
|
|
192
241
|
|
|
193
242
|
Local preflight before upload-url:
|
|
194
243
|
images <= 8MB (jpeg/png/gif/webp)
|
|
195
|
-
video <=
|
|
244
|
+
video <= 16GB (mp4/mov/webm). X publish: default 20 min / 8 GB; Premium and Premium+ 125 min / 16 GB.
|
|
196
245
|
Oversized video error includes an ffmpeg compress one-liner.
|
|
197
246
|
`,memory:`dun memory collections list|create|get|update|delete
|
|
198
247
|
dun memory list|save|search|delete
|
|
@@ -217,49 +266,60 @@ dun skill path`,update:`dun update
|
|
|
217
266
|
(no args) Check npm and upgrade via detected package manager
|
|
218
267
|
--check Report current vs latest only
|
|
219
268
|
--force Reinstall latest even if versions match
|
|
269
|
+
--yes Needed for a silent-skipped major bump
|
|
270
|
+
|
|
271
|
+
On a human TTY with a global npm/bun/pnpm/yarn install, dun auto-upgrades
|
|
272
|
+
after a successful command. Agents, CI, --json, and npx never auto-update.
|
|
273
|
+
Disable with --no-update or DUN_UPDATE=0.
|
|
220
274
|
|
|
221
275
|
Aliases: dun upgrade, dun self-update
|
|
222
|
-
`,upgrade:"Alias of update","self-update":"Alias of update"}[e]??
|
|
223
|
-
`),
|
|
224
|
-
`).map((n)=>n.length))}function
|
|
225
|
-
CLI v${
|
|
226
|
-
`)}import{spawn as
|
|
227
|
-
Next: dun workspace list && dun workspace use <id|slug>`}async function
|
|
228
|
-
${
|
|
276
|
+
`,upgrade:"Alias of update","self-update":"Alias of update"}[e]??re}var vt=["DunSocial","---------"].join(`
|
|
277
|
+
`),Ce="DunSocial";var Wn=Te(vt),qn=Te(Ce);function Te(e){return Math.max(...e.split(`
|
|
278
|
+
`).map((n)=>n.length))}function x(e=process.stderr){if(!e.isTTY)return!1;if(process.env.CI==="true"||process.env.CI==="1")return!1;if(process.env.GITHUB_ACTIONS||process.env.TF_BUILD||process.env.GITLAB_CI)return!1;if(process.env.DUN_JSON==="1")return!1;return!0}function Ue(){return`DunSocial CLI v${T}`}function Ee(){return`${Ce}
|
|
279
|
+
CLI v${T}`}var xt="https://dunsocial.com/docs/cli";function Rt(e){if(!e.token)return"signed_out";if(!e.workspaceId)return"needs_workspace";return"ready"}function Ne(e){let n=Rt(e),t=[`DunSocial CLI v${T}`,""];if(n==="signed_out")t.push("Not signed in.",""," Next: dun auth login");else if(n==="needs_workspace")t.push("Signed in — pick a workspace.",""," Next: dun workspace list"," dun workspace use <slug>");else{let i=e.config.defaultWorkspaceSlug||e.config.defaultWorkspaceName||e.workspaceId||"workspace";t.push(`Ready | workspace: ${i}`,""," Tip: dun accounts list"," dun posts list")}return t.push(""," dun --help all commands",` Docs ${xt}`,""),t.join(`
|
|
280
|
+
`)}import{spawn as $t}from"node:child_process";class ve{ctx;constructor(e){this.ctx=e}async request(e){let n=e.method??"GET",t=new URL(e.path.startsWith("http")?e.path:`${this.ctx.apiUrl}${e.path}`);if(e.query)for(let[l,u]of Object.entries(e.query)){if(u===void 0||u===null||u==="")continue;t.searchParams.set(l,String(u))}let i={Accept:"application/json","User-Agent":`dunsocial-cli/${T}`,...e.headers??{}};if(e.auth!==!1)i.Authorization=`Bearer ${y(this.ctx)}`;if(e.workspace)i["X-Workspace-Id"]=I(this.ctx);let a;if(e.body!==void 0)i["Content-Type"]="application/json",a=JSON.stringify(e.body);R(this.ctx.debug,`${n} ${t.toString()}`);let r;try{r=await fetch(t,{method:n,headers:i,body:a})}catch(l){throw new d(`Network error: ${l.message}`,c.NETWORK,{cause:String(l)})}let s=await r.text(),o=null;if(s)try{o=JSON.parse(s)}catch{o=null}if(R(this.ctx.debug,`<- ${r.status}`,{ok:r.ok,bodyPreview:s.slice(0,300)}),!r.ok){let l=o?.error||o?.message||(s?s.slice(0,300):`HTTP ${r.status}`);throw new d(l,ke(r.status),Lt(r.status,o,s))}if(o&&typeof o==="object"&&"success"in o){if(o.success===!1)throw new d(o.error||o.message||"Request failed",c.VALIDATION,o);return o.data}return o??void 0}get(e,n){return this.request({...n,method:"GET",path:e})}post(e,n,t){return this.request({...t,method:"POST",path:e,body:n})}patch(e,n,t){return this.request({...t,method:"PATCH",path:e,body:n})}delete(e,n,t){return this.request({...t,method:"DELETE",path:e,body:n})}}function b(e){return new ve(e)}function Lt(e,n,t){if(e===402&&n&&typeof n==="object"){let i=n;return{billingUrl:typeof i.billingUrl==="string"?i.billingUrl:void 0,subscriptionStatus:typeof i.subscriptionStatus==="string"?i.subscriptionStatus:void 0,workspaceSlug:typeof i.workspaceSlug==="string"?i.workspaceSlug:void 0}}return{status:e,body:n??t}}async function xe(e,n,t,i){switch(n){case"login":return Ot(e,t);case"logout":return _t(e);case"whoami":return jt(e);case"status":return Mt(e);default:throw new d(`Unknown auth command "${n??""}". Try: login, logout, whoami, status`,c.USAGE)}}async function Ot(e,n){let t=f(n,"token")||process.env.DUN_TOKEN;if(t)return Dt(e,t);return Pt(e,n)}function q(e){if(e)return"Logged in.";return`Logged in.
|
|
281
|
+
Next: dun workspace list && dun workspace use <id|slug>`}async function Dt(e,n){let t={...e,token:n},a=await b(t).get("/api/user/profile");if(await P({...e.config,apiUrl:e.apiUrl,token:n,defaultWorkspaceId:e.config.defaultWorkspaceId,defaultWorkspaceSlug:e.config.defaultWorkspaceSlug,defaultWorkspaceName:e.config.defaultWorkspaceName}),!e.json&&x(process.stdout)){p(q(Boolean(e.config.defaultWorkspaceId)),e);return}p({user:{id:a.id,name:a.name,email:a.email},configPath:D(),method:"token",message:q(Boolean(e.config.defaultWorkspaceId))},e)}async function Pt(e,n){let t=b({...e,token:void 0}),i=await t.post("/api/cli/auth/start",{clientName:"dun-cli"},{auth:!1}),a=!h(n,"no-browser");if(!e.json){if(x(process.stderr))process.stderr.write(`
|
|
282
|
+
${Ee()}
|
|
229
283
|
|
|
230
284
|
`);else process.stderr.write(`
|
|
231
285
|
DunSocial CLI login
|
|
232
286
|
`),process.stderr.write(`───────────────────
|
|
233
287
|
`);process.stderr.write(`In your browser, open:
|
|
234
|
-
${
|
|
288
|
+
${i.verificationUrlComplete}
|
|
235
289
|
|
|
236
|
-
`),process.stderr.write(`Or go to ${
|
|
237
|
-
`),process.stderr.write(` ${
|
|
290
|
+
`),process.stderr.write(`Or go to ${i.verificationUrl} and enter code:
|
|
291
|
+
`),process.stderr.write(` ${i.userCode}
|
|
238
292
|
|
|
239
293
|
`),process.stderr.write(`Waiting for approval...
|
|
240
|
-
`)}else process.stderr.write(JSON.stringify({ok:!0,phase:"waiting",userCode:
|
|
241
|
-
`);if(
|
|
242
|
-
Subscribe: ${t}`:n}function
|
|
294
|
+
`)}else process.stderr.write(JSON.stringify({ok:!0,phase:"waiting",userCode:i.userCode,verificationUrlComplete:i.verificationUrlComplete})+`
|
|
295
|
+
`);if(a&&process.stdout.isTTY)try{await qt(i.verificationUrlComplete)}catch{}let r=Math.max(3,i.interval||5)*1000,s=Date.now()+(i.expiresIn||900)*1000;while(Date.now()<s){await Wt(r);let o=await t.post("/api/cli/auth/poll",{deviceCode:i.deviceCode},{auth:!1});if(o.status==="pending")continue;if(o.status==="expired")throw new d("Login expired. Run dun auth login again.",c.AUTH);if(o.status==="denied")throw new d("Login was denied in the browser.",c.AUTH);if(o.status==="approved"){let l=o.token,u={...e,token:l},m=b(u),g=await m.get("/api/user/profile"),w={...e.config,apiUrl:e.apiUrl,token:l,defaultWorkspaceId:o.workspaceId||e.config.defaultWorkspaceId,defaultWorkspaceSlug:e.config.defaultWorkspaceSlug,defaultWorkspaceName:e.config.defaultWorkspaceName};if(o.workspaceId)try{let N=(await m.get("/api/workspaces")).find((mt)=>mt.id===o.workspaceId);if(N)w.defaultWorkspaceId=N.id,w.defaultWorkspaceSlug=N.slug,w.defaultWorkspaceName=N.name}catch{}await P(w);let k=Boolean(w.defaultWorkspaceId);if(!e.json&&x(process.stdout)){p(q(k),e);return}p({user:{id:g.id,name:g.name,email:g.email},workspaceId:w.defaultWorkspaceId??null,configPath:D(),method:"device",message:q(k)},e);return}}throw new d("Login timed out. Run dun auth login again.",c.AUTH)}async function _t(e){if(await ye(),!e.json&&x(process.stdout)){p("Logged out. Config cleared.",e);return}p({message:"Logged out. Config cleared.",configPath:D()},e)}async function jt(e){y(e);let t=await b(e).get("/api/user/profile");p({id:t.id,name:t.name,email:t.email,image:t.image??null,apiUrl:e.apiUrl,workspaceId:e.workspaceId??null},e)}async function Mt(e){let n=Boolean(e.token),t=null,i=null;if(n)try{t=await b(e).get("/api/user/profile")}catch(a){i=a instanceof Error?a.message:String(a)}p({authenticated:Boolean(t),tokenPresent:n,apiUrl:e.apiUrl,workspaceId:e.workspaceId??null,workspaceSlug:e.config.defaultWorkspaceSlug??null,workspaceName:e.config.defaultWorkspaceName??null,configPath:D(),user:t?{id:t.id,name:t.name,email:t.email}:null,error:i},e)}function Wt(e){return new Promise((n)=>setTimeout(n,e))}async function qt(e){let n=process.platform,t=n==="darwin"?["open",e]:n==="win32"?["cmd","/c","start","",e]:["xdg-open",e];await new Promise((i,a)=>{let r=$t(t[0],t.slice(1),{stdio:"ignore",windowsHide:!0});r.on("error",a),r.on("close",()=>i())})}function Re(e){return{id:e.id,name:e.name,slug:e.slug,role:e.role,timezone:e.timezone,plan:e.subscription?.planName??null,status:e.subscription?.status??null,hasAccess:e.subscription?.hasAccess??null,accessMessage:e.subscription?.accessMessage??null,billingUrl:e.subscription?.billingUrl??null,cancelAtPeriodEnd:e.subscription?.cancelAtPeriodEnd??null,currentPeriodEnd:e.subscription?.currentPeriodEnd??null}}function Vt(e){return e?.hasAccess===!1}function Bt(e){if(!Vt(e.subscription))return null;let n=e.subscription?.accessMessage??`Workspace "${e.name}" is not on a paid plan.`,t=e.subscription?.billingUrl;return t?`${n}
|
|
296
|
+
Subscribe: ${t}`:n}function Gt(){return"Every workspace is unpaid. Open a billing URL above — posts and drafts stay locked until you subscribe."}function L(e,n){if(n.json||!process.stdout.isTTY)return;let t=e.map((i)=>Bt(i)).filter((i)=>Boolean(i));if(t.length===0)return;if(process.stdout.write(`${t.join(`
|
|
243
297
|
`)}
|
|
244
|
-
`),t.length===e.length&&e.length>0)process.stdout.write(`${
|
|
245
|
-
`)}async function ke(e,n,t,o){b(e);let c=y(e);switch(n){case"list":case"ls":{let i=await c.get("/api/workspaces");m(i.map(he),{...e,emptyMessage:"No workspaces yet."}),O(i,e);return}case"use":{let i=o[0];if(!i)throw new l("Usage: dun workspace use <id|slug>",a.USAGE);let r=(await c.get("/api/workspaces")).find((d)=>d.id===i||d.slug===i);if(!r)throw new l(`Workspace not found: ${i}. Run dun workspace list`,a.NOT_FOUND);await P({...e.config,apiUrl:e.apiUrl,token:e.token,defaultWorkspaceId:r.id,defaultWorkspaceSlug:r.slug,defaultWorkspaceName:r.name}),m({id:r.id,name:r.name,slug:r.slug,role:r.role,hasAccess:r.subscription?.hasAccess??null,accessMessage:r.subscription?.accessMessage??null,billingUrl:r.subscription?.billingUrl??null,message:`Default workspace set to ${r.name}`},e),O([r],e);return}case"current":{if(!e.workspaceId&&!e.config.defaultWorkspaceId)throw new l("No workspace selected. Run dun workspace use <id|slug>",a.VALIDATION);let i=e.workspaceId||e.config.defaultWorkspaceId;try{let r=(await c.get("/api/workspaces")).find((d)=>d.id===i||d.slug===i);if(r){m({id:r.id,name:r.name,slug:r.slug,role:r.role,timezone:r.timezone,hasAccess:r.subscription?.hasAccess??null,accessMessage:r.subscription?.accessMessage??null,billingUrl:r.subscription?.billingUrl??null},e),O([r],e);return}}catch{}m({id:i,name:e.config.defaultWorkspaceName??null,slug:e.config.defaultWorkspaceSlug??null},e);return}default:throw new l(`Unknown workspace command "${n??""}". Try: list, use, current`,a.USAGE)}}async function ye(e,n,t,o){b(e),A(e);let c=y(e);switch(n){case void 0:case"list":case"ls":{let i=await c.get("/api/social-accounts",{workspace:!0}),s=g(t,"platform","p")?.toLowerCase(),r=s?i.filter((d)=>{let u=(d.providerName||"").toLowerCase();return({x:["x","twitter"],twitter:["x","twitter"],linkedin:["linkedin"],"linkedin-business":["linkedin business","linkedin-business","linkedin_business"],bluesky:["bluesky"],threads:["threads"],reddit:["reddit"],pinterest:["pinterest"],instagram:["instagram"],youtube:["youtube"],tiktok:["tiktok"]}[s]??[s]).some((f)=>u.includes(f))}):i;m(r.map((d)=>({id:d.id,providerName:d.providerName,username:d.username??null,displayName:d.displayName??null,isConnected:d.isConnected,expiresAt:d.expiresAt??null})),{...e,emptyMessage:s?`No ${s} accounts connected. Connect one in the DunSocial dashboard.`:"No accounts connected. Connect one in the DunSocial dashboard."});try{let d=await c.get(`/api/workspaces/${A(e)}`,{workspace:!0});O([d],e)}catch{}return}default:throw new l(`Unknown accounts command "${n}". Try: list (or: dun accounts reddit flairs <subreddit>)`,a.USAGE)}}import{readFileSync as yt}from"node:fs";var ht=/^(\d+)\s*(s|sec|secs|second|seconds|m|min|mins|minute|minutes|h|hr|hrs|hour|hours|d|day|days|w|week|weeks)$/i;function Y(e,n=new Date){let t=e.trim();if(!t)throw new l("Empty time value",a.VALIDATION);let o=t.match(ht);if(o){let s=Number(o[1]),r=o[2].toLowerCase(),d=kt(r)*s;return new Date(n.getTime()+d)}let c=t.match(/^in\s+(.+)$/i);if(c)return Y(c[1],n);let i=new Date(t);if(Number.isNaN(i.getTime()))throw new l(`Invalid time "${e}". Use ISO-8601 (2026-06-01T09:00:00Z) or relative (2h, 30m, 1d).`,a.VALIDATION);return i}function kt(e){switch(e){case"s":case"sec":case"secs":case"second":case"seconds":return 1000;case"m":case"min":case"mins":case"minute":case"minutes":return 60000;case"h":case"hr":case"hrs":case"hour":case"hours":return 3600000;case"d":case"day":case"days":return 86400000;case"w":case"week":case"weeks":return 604800000;default:throw new l(`Unknown time unit: ${e}`,a.VALIDATION)}}function M(e){if(e.at&&e.in)throw new l("Pass only one of --at or --in",a.VALIDATION);if(e.at)return Y(e.at).toISOString();if(e.in)return Y(e.in).toISOString();throw new l("Schedule time required. Pass --at <iso> or --in <duration> (e.g. 2h).",a.VALIDATION)}function be(e){if(!e)return[];return e.split(",").map((n)=>n.trim()).filter(Boolean)}function X(e,n){let t=g(e,"text","content","c","t");if(t)return t;let o=n?.reddit;if(o&&typeof o==="object"&&!Array.isArray(o)&&typeof o.title==="string"&&o.title.trim())return o.title.trim();throw new l("Missing --text",a.VALIDATION)}function j(e){let n=U(e,"accounts","account","a")??be(g(e,"accounts","account","a"));if(!n||n.length===0)throw new l("Missing --accounts <id,id>",a.VALIDATION);return n}function q(e){return U(e,"media","media-urls")??be(g(e,"media","media-urls"))}function L(e){let n=g(e,"meta","metadata","meta-json");if(!n)return;try{let t=JSON.parse(n);if(!t||typeof t!=="object"||Array.isArray(t))throw Error("metadata must be a JSON object");return t}catch(t){throw new l(`Invalid --meta JSON: ${t.message}`,a.VALIDATION)}}var bt=new Set(["published","failed","cancelled"]);async function It(e){await new Promise((n)=>setTimeout(n,e))}async function At(e,n,t,o){let c=Date.now()+o*1000,i=new Map,s=1500;while(i.size<t.length){if(Date.now()>c){let u=t.filter((p)=>!i.has(p.id)).map((p)=>p.id);throw new l(`Timed out waiting for publish after ${o}s. Still pending: ${u.join(", ")}. Poll with: dun posts get <id> --json`,a.NETWORK,{pending:u})}for(let u of t){if(i.has(u.id))continue;let p=await e.get(`/api/posts/${u.id}`,{workspace:!0});if(R(n.debug,`wait status ${u.id}=${p.status}`),bt.has(p.status))i.set(u.id,p)}if(i.size>=t.length)break;await It(s),s=Math.min(s*1.4,8000)}let r=t.map((u)=>i.get(u.id)),d=r.filter((u)=>u.status==="failed");if(d.length>0){let u=d.map((p)=>`${p.id}: ${p.error||"publish failed"}`).join("; ");throw new l(`Publish failed — ${u}`,a.VALIDATION,{posts:r})}return r}async function St(e,n){let t=[],o=n.meta?.reddit??null;if(!o||typeof o!=="object")throw new l("Reddit accounts require --meta with a reddit object, e.g. "+`--meta '{"reddit":{"subreddit":"…","kind":"self","title":"…","flairId":"…"}}'`,a.VALIDATION);let c=(o.subreddit??"").trim().replace(/^\/?r\//i,""),i=(o.title??"").trim(),s=(o.kind??"").trim(),r=(o.flairId??"").trim(),d=(o.flairText??"").trim();if(!c)throw new l("metadata.reddit.subreddit is required",a.VALIDATION);if(!i)throw new l("metadata.reddit.title is required",a.VALIDATION);if(i.length>300)throw new l(`Reddit title exceeds 300 characters: ${i.length}/300`,a.VALIDATION);if(!["self","link","image","video"].includes(s))throw new l("metadata.reddit.kind must be one of: self, link, image, video",a.VALIDATION);if(t.push(`kind=${s}`),t.push(`titleLength=${i.length}`),d&&!r)throw new l(`Can't set flairText without flairId for r/${c}. Run: dun reddit flairs ${c} --json`,a.VALIDATION);if(s==="link"){if(!o.url?.trim())throw new l("Reddit link posts require metadata.reddit.url",a.VALIDATION);try{new URL(o.url)}catch{throw new l("metadata.reddit.url is invalid",a.VALIDATION)}if(n.media.length>0)throw new l("Reddit link posts do not accept --media",a.VALIDATION)}else if(s==="image"||s==="video"){if(n.media.length!==1)throw new l(`Reddit ${s} posts require exactly one --media asset id`,a.VALIDATION)}else if(n.media.length>0)throw new l("Reddit self posts do not accept --media. Use kind image or video.",a.VALIDATION);let u=await e.get("/api/social-accounts/reddit/post-requirements",{workspace:!0,query:{subreddit:c}});if(t.push("fetched post-requirements"),u?.is_flair_required&&!r)throw new l(`r/${c} requires post flair. Discover ids with: dun reddit flairs ${c} --json`,a.VALIDATION);let p=u?.title_text_max_length??300;if(i.length>p)throw new l(`Reddit title too long for r/${c}: ${i.length}/${p}`,a.VALIDATION);if(u?.title_text_min_length&&i.length<u.title_text_min_length)throw new l(`Reddit title must be at least ${u.title_text_min_length} characters`,a.VALIDATION);if(r){let w=await e.get("/api/social-accounts/reddit/flair-options",{workspace:!0,query:{subreddit:c}});t.push(`fetched ${w.choices?.length??0} flairs`);let f=(w.choices??[]).find((h)=>h.id===r);if(!f)throw new l(`Unknown flairId "${r}" for r/${c}. Run: dun reddit flairs ${c} --json`,a.VALIDATION);if(f.textEditable===!1&&d&&d!==f.text)throw new l(`Customization not allowed for this flair template. Use flairId "${f.id}" alone`+(f.text?` (template text: "${f.text}")`:""),a.VALIDATION);t.push(`flairId=${f.id}`)}return{ok:!0,checks:t,reddit:{subreddit:c,kind:s,title:i,flairId:r||null,accounts:n.accounts,media:n.media,textPreview:n.text?.slice(0,120)??null,requirements:{is_flair_required:u?.is_flair_required??!1,title_text_max_length:p}}}}async function Ie(e,n,t,o){b(e),A(e);let c=y(e);switch(n){case"list":case"ls":{let i=await c.get("/api/posts",{workspace:!0,query:{status:g(t,"status","s"),socialAccountId:g(t,"account","social-account"),groupId:g(t,"group"),limit:E(t,"limit")??50,offset:E(t,"offset")??0}});m(i,{...e,emptyMessage:"No posts yet. Schedule one with dun posts schedule."});return}case"get":{let i=o[0];if(!i)throw new l("Usage: dun posts get <id>",a.USAGE);let s=await c.get(`/api/posts/${i}`,{workspace:!0});m(s,e);return}case"validate":{let i=j(t),s=q(t),r=L(t)??{},d=X(t,r),u=await c.post("/api/posts/validate",{content:d,socialAccountIds:i,mediaUrls:s,metadata:r},{workspace:!0});if(!u.valid){let f=u.issues.map((h)=>h.message).join("; ");throw new l(f||"Post validation failed",a.VALIDATION,u)}let p=Boolean(r.reddit),w=p;if(!p){let f=await c.get("/api/social-accounts",{workspace:!0});w=i.some((h)=>(f.find((v)=>v.id===h)?.providerName||"").toLowerCase().includes("reddit"))}if(w){let f=await St(c,{accounts:i,text:d,media:s,meta:r});m({ok:!0,...u,reddit:f},e);return}m({ok:!0,...u},e);return}case"schedule":{let i=L(t)??{},s=X(t,i),r=j(t),d=M({at:g(t,"at"),in:g(t,"in")}),u=await c.post("/api/posts/schedule",{content:s,socialAccountIds:r,scheduledAt:d,mediaUrls:q(t),metadata:i},{workspace:!0});m(u,e);return}case"publish":case"publish-now":{let i=L(t)??{},s=X(t,i),r=j(t),d=await c.post("/api/posts/publish-now",{content:s,socialAccountIds:r,mediaUrls:q(t),metadata:i,naturalPosting:k(t,"natural","natural-posting")},{workspace:!0});if(!k(t,"wait")){m(d,e);return}let p=Array.isArray(d)?d:[];if(p.length===0||!p.every((h)=>typeof h?.id==="string"))throw new l("publish --wait expected an array of created posts with ids",a.NETWORK,{data:d});let w=E(t,"wait-timeout","timeout")??120,f=await At(c,e,p,w);m({queued:p,posts:f,status:f.every((h)=>h.status==="published")?"published":f[0]?.status},e);return}case"reschedule":{let i=o[0];if(!i)throw new l("Usage: dun posts reschedule <id> --at|--in",a.USAGE);let r={scheduledAt:M({at:g(t,"at"),in:g(t,"in")})},d=g(t,"text","content","c","t");if(d)r.content=d;let u=q(t);if(u.length)r.mediaUrls=u;let p=L(t);if(p)r.metadata=p;if(!r.content){let f=await c.get(`/api/posts/${i}`,{workspace:!0});if(r.content=f.content,!r.metadata&&f.metadata)r.metadata=f.metadata;if(!r.mediaUrls&&f.mediaUrls)r.mediaUrls=f.mediaUrls}let w=await c.post(`/api/posts/${i}/reschedule`,r,{workspace:!0});m(w,e);return}case"cancel":{let i=o[0];if(!i)throw new l("Usage: dun posts cancel <id>",a.USAGE);let s=await c.post(`/api/posts/${i}/cancel`,void 0,{workspace:!0});m(s??{id:i,status:"cancelled"},e);return}case"delete":case"rm":{let i=o[0];if(!i)throw new l("Usage: dun posts delete <id> [--yes]",a.USAGE);if(!e.yes&&!k(t,"yes","y"))throw new l("Refusing to delete without --yes (or CI/yes mode)",a.VALIDATION);let s=await c.delete(`/api/posts/${i}`,void 0,{workspace:!0});m(s??{id:i,deleted:!0},e);return}case"x-cap":case"xcap":{let i=await c.get("/api/posts/x-cap-usage",{workspace:!0});m(i,e);return}case"schedule-thread":case"publish-thread":case"publish-thread-now":{let i=g(t,"file","f"),s=g(t,"account","accounts","a")??j(t)[0];if(!s)throw new l("Missing --account <xAccountId>",a.VALIDATION);let r;if(i){let u;try{u=yt(i,"utf8")}catch(w){throw new l(`Could not read file: ${w.message}`,a.VALIDATION)}let p=JSON.parse(u);if(Array.isArray(p))r=p;else if(p&&typeof p==="object"&&Array.isArray(p.tweets))r=p.tweets;else throw new l("Thread file must be an array of tweets or { tweets: [] }",a.VALIDATION)}else{let u=g(t,"text","content");if(!u)throw new l('Provide --file thread.json (array of {content}) or --text "tweet1||tweet2"',a.VALIDATION);r=u.split("||").map((p)=>({content:p.trim()})).filter((p)=>p.content)}if(r.length<2)throw new l("Threads require at least 2 tweets",a.VALIDATION);if(n==="schedule-thread"){let u=M({at:g(t,"at"),in:g(t,"in")}),p=await c.post("/api/posts/schedule-thread",{socialAccountId:s,tweets:r,scheduledAt:u,metadata:L(t)??{}},{workspace:!0});m(p,e);return}let d=await c.post("/api/posts/publish-thread-now",{socialAccountId:s,tweets:r,metadata:L(t)??{}},{workspace:!0});m(d,e);return}default:throw new l(`Unknown posts command "${n??""}". Try: list, get, validate, schedule, publish, reschedule, cancel, delete, x-cap, schedule-thread, publish-thread`,a.USAGE)}}async function Ae(e,n,t,o){b(e),A(e);let c=y(e);switch(n){case"list":case"ls":{let i=await c.get("/api/drafts",{workspace:!0,query:{limit:E(t,"limit")??50,offset:E(t,"offset")??0}});m(i,{...e,emptyMessage:'No drafts yet. Create one with dun drafts create --text "..."'});return}case"get":{let i=o[0];if(!i)throw new l("Usage: dun drafts get <id>",a.USAGE);let s=await c.get(`/api/drafts/${i}`,{workspace:!0});m(s,e);return}case"create":{let i=g(t,"text","content","c","t");if(!i)throw new l("Missing --text",a.VALIDATION);let s=await c.post("/api/drafts",{content:i,name:g(t,"name","n"),socialAccountIds:U(t,"accounts","account","a")??[],selectedPlatforms:U(t,"platforms","platform","p")??[],mediaUrls:U(t,"media")??[]},{workspace:!0});m(s,e);return}case"update":{let i=o[0];if(!i)throw new l("Usage: dun drafts update <id> --text ...",a.USAGE);let s={id:i},r=g(t,"text","content","c","t");if(r)s.content=r;let d=g(t,"name","n");if(d!==void 0)s.name=d;let u=U(t,"accounts","account","a");if(u)s.socialAccountIds=u;let p=U(t,"platforms","platform","p");if(p)s.selectedPlatforms=p;let w=U(t,"media");if(w)s.mediaUrls=w;let f=await c.patch(`/api/drafts/${i}`,s,{workspace:!0});m(f,e);return}case"delete":case"rm":{let i=o[0];if(!i)throw new l("Usage: dun drafts delete <id> [--yes]",a.USAGE);if(!e.yes&&!k(t,"yes","y"))throw new l("Refusing to delete without --yes",a.VALIDATION);let s=await c.delete(`/api/drafts/${i}`,void 0,{workspace:!0});m(s??{id:i,deleted:!0},e);return}default:throw new l(`Unknown drafts command "${n??""}". Try: list, get, create, update, delete`,a.USAGE)}}import{readFileSync as Tt,existsSync as Nt,statSync as Et}from"node:fs";import{basename as Ut}from"node:path";var Se=["image/jpeg","image/png","image/gif","image/webp"],Te=["video/mp4","video/quicktime","video/webm"];function Ne(e){if(e<1024)return`${e}B`;if(e<1048576)return`${(e/1024).toFixed(1)}KB`;return`${(e/1048576).toFixed(1)}MB`}function Ee(e){let{mimeType:n,fileSize:t}=e,o=Se.includes(n),c=Te.includes(n);if(!o&&!c)return{ok:!1,message:`Unsupported MIME type "${n}". Allowed images: ${Se.join(", ")}; videos: ${Te.join(", ")}`};if(c&&t>536870912)return{ok:!1,message:`Video files must be less than 512MB (got ${Ne(t)}). Compress first, e.g.:
|
|
246
|
-
ffmpeg -i input.mp4 -vf scale=-2:1080 -c:v libx264 -crf 23 -c:a aac output.mp4`};if(
|
|
247
|
-
Example: dun reddit flairs tamilyapping --json`,
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
298
|
+
`),t.length===e.length&&e.length>0)process.stdout.write(`${Gt()}
|
|
299
|
+
`)}async function Le(e,n,t,i){y(e);let a=b(e);switch(n){case"list":case"ls":{let r=await a.get("/api/workspaces");p(r.map(Re),{...e,emptyMessage:"No workspaces yet."}),L(r,e);return}case"use":{let r=i[0];if(!r)throw new d("Usage: dun workspace use <id|slug>",c.USAGE);let o=(await a.get("/api/workspaces")).find((l)=>l.id===r||l.slug===r);if(!o)throw new d(`Workspace not found: ${r}. Run dun workspace list`,c.NOT_FOUND);await P({...e.config,apiUrl:e.apiUrl,token:e.token,defaultWorkspaceId:o.id,defaultWorkspaceSlug:o.slug,defaultWorkspaceName:o.name}),p({id:o.id,name:o.name,slug:o.slug,role:o.role,hasAccess:o.subscription?.hasAccess??null,accessMessage:o.subscription?.accessMessage??null,billingUrl:o.subscription?.billingUrl??null,message:`Default workspace set to ${o.name}`},e),L([o],e);return}case"current":{if(!e.workspaceId&&!e.config.defaultWorkspaceId)throw new d("No workspace selected. Run dun workspace use <id|slug>",c.VALIDATION);let r=e.workspaceId||e.config.defaultWorkspaceId;try{let o=(await a.get("/api/workspaces")).find((l)=>l.id===r||l.slug===r);if(o){p({id:o.id,name:o.name,slug:o.slug,role:o.role,timezone:o.timezone,hasAccess:o.subscription?.hasAccess??null,accessMessage:o.subscription?.accessMessage??null,billingUrl:o.subscription?.billingUrl??null},e),L([o],e);return}}catch{}p({id:r,name:e.config.defaultWorkspaceName??null,slug:e.config.defaultWorkspaceSlug??null},e);return}default:throw new d(`Unknown workspace command "${n??""}". Try: list, use, current`,c.USAGE)}}async function $e(e,n,t,i){y(e),I(e);let a=b(e);switch(n){case void 0:case"list":case"ls":{let r=await a.get("/api/social-accounts",{workspace:!0}),s=f(t,"platform","p")?.toLowerCase(),o=s?r.filter((l)=>{let u=(l.providerName||"").toLowerCase();return({x:["x","twitter"],twitter:["x","twitter"],linkedin:["linkedin"],"linkedin-business":["linkedin business","linkedin-business","linkedin_business"],bluesky:["bluesky"],threads:["threads"],reddit:["reddit"],pinterest:["pinterest"],instagram:["instagram"],youtube:["youtube"],tiktok:["tiktok"],discord:["discord"]}[s]??[s]).some((w)=>u.includes(w))}):r;p(o.map((l)=>({id:l.id,providerName:l.providerName,username:l.username??null,displayName:l.displayName??null,isConnected:l.isConnected,expiresAt:l.expiresAt??null})),{...e,emptyMessage:s?`No ${s} accounts connected. Connect one in the DunSocial dashboard.`:"No accounts connected. Connect one in the DunSocial dashboard."});try{let l=await a.get(`/api/workspaces/${I(e)}`,{workspace:!0});L([l],e)}catch{}return}default:throw new d(`Unknown accounts command "${n}". Try: list (or: dun accounts reddit flairs <subreddit>)`,c.USAGE)}}import{readFileSync as Ht}from"node:fs";var Ft=/^(\d+)\s*(s|sec|secs|second|seconds|m|min|mins|minute|minutes|h|hr|hrs|hour|hours|d|day|days|w|week|weeks)$/i;function oe(e,n=new Date){let t=e.trim();if(!t)throw new d("Empty time value",c.VALIDATION);let i=t.match(Ft);if(i){let s=Number(i[1]),o=i[2].toLowerCase(),l=Kt(o)*s;return new Date(n.getTime()+l)}let a=t.match(/^in\s+(.+)$/i);if(a)return oe(a[1],n);let r=new Date(t);if(Number.isNaN(r.getTime()))throw new d(`Invalid time "${e}". Use ISO-8601 (2026-06-01T09:00:00Z) or relative (2h, 30m, 1d).`,c.VALIDATION);return r}function Kt(e){switch(e){case"s":case"sec":case"secs":case"second":case"seconds":return 1000;case"m":case"min":case"mins":case"minute":case"minutes":return 60000;case"h":case"hr":case"hrs":case"hour":case"hours":return 3600000;case"d":case"day":case"days":return 86400000;case"w":case"week":case"weeks":return 604800000;default:throw new d(`Unknown time unit: ${e}`,c.VALIDATION)}}function V(e){if(e.at&&e.in)throw new d("Pass only one of --at or --in",c.VALIDATION);if(e.at)return oe(e.at).toISOString();if(e.in)return oe(e.in).toISOString();throw new d("Schedule time required. Pass --at <iso> or --in <duration> (e.g. 2h).",c.VALIDATION)}function De(e){if(!e)return[];return e.split(",").map((n)=>n.trim()).filter(Boolean)}function se(e,n){let t=f(e,"text","content","c","t");if(t)return t;let i=n?.reddit;if(i&&typeof i==="object"&&!Array.isArray(i)&&typeof i.title==="string"&&i.title.trim())return i.title.trim();throw new d("Missing --text",c.VALIDATION)}function B(e){let n=E(e,"accounts","account","a")??De(f(e,"accounts","account","a"));if(!n||n.length===0)throw new d("Missing --accounts <id,id>",c.VALIDATION);return n}function G(e){return E(e,"media","media-urls")??De(f(e,"media","media-urls"))}function zt(e,n){if(e?.reddit)return!0;return n.some((t)=>t.toLowerCase().includes("reddit"))}function Jt(e){let n=e.existing.metadata&&typeof e.existing.metadata==="object"&&!Array.isArray(e.existing.metadata)?e.existing.metadata:{};return{scheduledAt:e.scheduledAt,content:e.text??e.existing.content,mediaUrls:e.media&&e.media.length>0?e.media:e.existing.mediaUrls,metadata:e.meta?{...n,...e.meta}:n}}function O(e){let n=f(e,"meta","metadata","meta-json");if(!n)return;try{let t=JSON.parse(n);if(!t||typeof t!=="object"||Array.isArray(t))throw Error("metadata must be a JSON object");return t}catch(t){throw new d(`Invalid --meta JSON: ${t.message}`,c.VALIDATION)}}var Yt=new Set(["published","failed","cancelled"]);async function Xt(e){await new Promise((n)=>setTimeout(n,e))}async function Zt(e,n,t,i){let a=Date.now()+i*1000,r=new Map,s=1500;while(r.size<t.length){if(Date.now()>a){let u=t.filter((m)=>!r.has(m.id)).map((m)=>m.id);throw new d(`Timed out waiting for publish after ${i}s. Still pending: ${u.join(", ")}. Poll with: dun posts get <id> --json`,c.NETWORK,{pending:u})}for(let u of t){if(r.has(u.id))continue;let m=await e.get(`/api/posts/${u.id}`,{workspace:!0});if(R(n.debug,`wait status ${u.id}=${m.status}`),Yt.has(m.status))r.set(u.id,m)}if(r.size>=t.length)break;await Xt(s),s=Math.min(s*1.4,8000)}let o=t.map((u)=>r.get(u.id)),l=o.filter((u)=>u.status==="failed");if(l.length>0){let u=l.map((m)=>`${m.id}: ${m.error||"publish failed"}`).join("; ");throw new d(`Publish failed — ${u}`,c.VALIDATION,{posts:o})}return o}async function Oe(e,n,t){if(t.meta?.reddit){await ae(e,{accounts:n,text:t.text,media:t.media,meta:t.meta});return}let i=await e.get("/api/social-accounts",{workspace:!0}),a=n.map((r)=>i.find((o)=>o.id===r)?.providerName||"");if(!zt(t.meta,a))return;await ae(e,{accounts:n,text:t.text,media:t.media,meta:t.meta})}async function ae(e,n){let t=[],i=n.meta?.reddit??null;if(!i||typeof i!=="object")throw new d("Reddit accounts require --meta with a reddit object, e.g. "+`--meta '{"reddit":{"subreddit":"…","kind":"self","title":"…","flairId":"…"}}'`,c.VALIDATION);let a=(i.subreddit??"").trim().replace(/^\/?r\//i,""),r=(i.title??"").trim(),s=(i.kind??"").trim(),o=(i.flairId??"").trim(),l=(i.flairText??"").trim();if(!a)throw new d("metadata.reddit.subreddit is required",c.VALIDATION);if(!r)throw new d("metadata.reddit.title is required",c.VALIDATION);if(r.length>300)throw new d(`Reddit title exceeds 300 characters: ${r.length}/300`,c.VALIDATION);if(!["self","link","image","video"].includes(s))throw new d("metadata.reddit.kind must be one of: self, link, image, video",c.VALIDATION);if(t.push(`kind=${s}`),t.push(`titleLength=${r.length}`),l&&!o)throw new d(`Can't set flairText without flairId for r/${a}. Run: dun reddit flairs ${a} --json`,c.VALIDATION);if(s==="link"){if(!i.url?.trim())throw new d("Reddit link posts require metadata.reddit.url",c.VALIDATION);try{new URL(i.url)}catch{throw new d("metadata.reddit.url is invalid",c.VALIDATION)}if(n.media.length>0)throw new d("Reddit link posts do not accept --media",c.VALIDATION)}else if(s==="image"||s==="video"){if(n.media.length!==1)throw new d(`Reddit ${s} posts require exactly one --media asset id`,c.VALIDATION)}else if(n.media.length>0)throw new d("Reddit self posts do not accept --media. Use kind image or video.",c.VALIDATION);let u=await e.get("/api/social-accounts/reddit/post-requirements",{workspace:!0,query:{subreddit:a}});if(t.push("fetched post-requirements"),u?.is_flair_required&&!o)throw new d(`r/${a} requires post flair. Discover ids with: dun reddit flairs ${a} --json`,c.VALIDATION);let m=u?.title_text_max_length??300;if(r.length>m)throw new d(`Reddit title too long for r/${a}: ${r.length}/${m}`,c.VALIDATION);if(u?.title_text_min_length&&r.length<u.title_text_min_length)throw new d(`Reddit title must be at least ${u.title_text_min_length} characters`,c.VALIDATION);if(o){let g=await e.get("/api/social-accounts/reddit/flair-options",{workspace:!0,query:{subreddit:a}});t.push(`fetched ${g.choices?.length??0} flairs`);let w=(g.choices??[]).find((k)=>k.id===o);if(!w)throw new d(`Unknown flairId "${o}" for r/${a}. Run: dun reddit flairs ${a} --json`,c.VALIDATION);if(w.textEditable===!1&&l&&l!==w.text)throw new d(`Customization not allowed for this flair template. Use flairId "${w.id}" alone`+(w.text?` (template text: "${w.text}")`:""),c.VALIDATION);t.push(`flairId=${w.id}`)}return{ok:!0,checks:t,reddit:{subreddit:a,kind:s,title:r,flairId:o||null,accounts:n.accounts,media:n.media,textPreview:n.text?.slice(0,120)??null,requirements:{is_flair_required:u?.is_flair_required??!1,title_text_max_length:m}}}}async function Pe(e,n,t,i){y(e),I(e);let a=b(e);switch(n){case"list":case"ls":{let r=await a.get("/api/posts",{workspace:!0,query:{status:f(t,"status","s"),socialAccountId:f(t,"account","social-account"),groupId:f(t,"group"),limit:U(t,"limit")??50,offset:U(t,"offset")??0}});p(r,{...e,emptyMessage:"No posts yet. Schedule one with dun posts schedule."});return}case"get":{let r=i[0];if(!r)throw new d("Usage: dun posts get <id>",c.USAGE);let s=await a.get(`/api/posts/${r}`,{workspace:!0});p(s,e);return}case"validate":{let r=B(t),s=G(t),o=O(t)??{},l=se(t,o),u=await a.post("/api/posts/validate",{content:l,socialAccountIds:r,mediaUrls:s,metadata:o},{workspace:!0});if(!u.valid){let w=u.issues.map((k)=>k.next?`${k.message} ${k.next}`:k.message).join("; ");throw new d(w||"Post validation failed",c.VALIDATION,u)}let m=Boolean(o.reddit),g=m;if(!m){let w=await a.get("/api/social-accounts",{workspace:!0});g=r.some((k)=>(w.find((N)=>N.id===k)?.providerName||"").toLowerCase().includes("reddit"))}if(g){let w=await ae(a,{accounts:r,text:l,media:s,meta:o});p({ok:!0,...u,reddit:w},e);return}p({ok:!0,...u},e);return}case"schedule":{let r=O(t)??{},s=se(t,r),o=B(t),l=G(t);await Oe(a,o,{text:s,media:l,meta:r});let u=V({at:f(t,"at"),in:f(t,"in")}),m=await a.post("/api/posts/schedule",{content:s,socialAccountIds:o,scheduledAt:u,mediaUrls:l,metadata:r},{workspace:!0});p(m,e);return}case"publish":case"publish-now":{let r=O(t)??{},s=se(t,r),o=B(t),l=G(t);await Oe(a,o,{text:s,media:l,meta:r});let u=await a.post("/api/posts/publish-now",{content:s,socialAccountIds:o,mediaUrls:l,metadata:r,naturalPosting:h(t,"natural","natural-posting")},{workspace:!0});if(!h(t,"wait")){p(u,e);return}let g=Array.isArray(u)?u:[];if(g.length===0||!g.every((v)=>typeof v?.id==="string"))throw new d("publish --wait expected an array of created posts with ids",c.NETWORK,{data:u});let w=U(t,"wait-timeout","timeout")??120,k=await Zt(a,e,g,w);p({queued:g,posts:k,status:k.every((v)=>v.status==="published")?"published":k[0]?.status},e);return}case"reschedule":{let r=i[0];if(!r)throw new d("Usage: dun posts reschedule <id> --at|--in",c.USAGE);let s=V({at:f(t,"at"),in:f(t,"in")}),o=await a.get(`/api/posts/${r}`,{workspace:!0}),l=Jt({scheduledAt:s,text:f(t,"text","content","c","t"),media:G(t),meta:O(t),existing:o}),u=await a.post(`/api/posts/${r}/reschedule`,l,{workspace:!0});p(u,e);return}case"cancel":{let r=i[0];if(!r)throw new d("Usage: dun posts cancel <id>",c.USAGE);let s=await a.post(`/api/posts/${r}/cancel`,void 0,{workspace:!0});p(s??{id:r,status:"cancelled"},e);return}case"delete":case"rm":{let r=i[0];if(!r)throw new d("Usage: dun posts delete <id> [--yes]",c.USAGE);if(!e.yes&&!h(t,"yes","y"))throw new d("Refusing to delete without --yes (or CI/yes mode)",c.VALIDATION);let s=await a.delete(`/api/posts/${r}`,void 0,{workspace:!0});p(s??{id:r,deleted:!0},e);return}case"x-cap":case"xcap":{let r=await a.get("/api/posts/x-cap-usage",{workspace:!0});p(r,e);return}case"schedule-thread":case"publish-thread":case"publish-thread-now":{let r=f(t,"file","f"),s=f(t,"account","accounts","a")??B(t)[0];if(!s)throw new d("Missing --account <xAccountId>",c.VALIDATION);let o;if(r){let u;try{u=Ht(r,"utf8")}catch(g){throw new d(`Could not read file: ${g.message}`,c.VALIDATION)}let m=JSON.parse(u);if(Array.isArray(m))o=m;else if(m&&typeof m==="object"&&Array.isArray(m.tweets))o=m.tweets;else throw new d("Thread file must be an array of tweets or { tweets: [] }",c.VALIDATION)}else{let u=f(t,"text","content");if(!u)throw new d('Provide --file thread.json (array of {content}) or --text "tweet1||tweet2"',c.VALIDATION);o=u.split("||").map((m)=>({content:m.trim()})).filter((m)=>m.content)}if(o.length<2)throw new d("Threads require at least 2 tweets",c.VALIDATION);if(n==="schedule-thread"){let u=V({at:f(t,"at"),in:f(t,"in")}),m=await a.post("/api/posts/schedule-thread",{socialAccountId:s,tweets:o,scheduledAt:u,metadata:O(t)??{}},{workspace:!0});p(m,e);return}let l=await a.post("/api/posts/publish-thread-now",{socialAccountId:s,tweets:o,metadata:O(t)??{}},{workspace:!0});p(l,e);return}default:throw new d(`Unknown posts command "${n??""}". Try: list, get, validate, schedule, publish, reschedule, cancel, delete, x-cap, schedule-thread, publish-thread`,c.USAGE)}}async function _e(e,n,t,i){y(e),I(e);let a=b(e);switch(n){case"list":case"ls":{let r=await a.get("/api/drafts",{workspace:!0,query:{limit:U(t,"limit")??50,offset:U(t,"offset")??0}});p(r,{...e,emptyMessage:'No drafts yet. Create one with dun drafts create --text "..."'});return}case"get":{let r=i[0];if(!r)throw new d("Usage: dun drafts get <id>",c.USAGE);let s=await a.get(`/api/drafts/${r}`,{workspace:!0});p(s,e);return}case"create":{let r=f(t,"text","content","c","t");if(!r)throw new d("Missing --text",c.VALIDATION);let s=await a.post("/api/drafts",{content:r,name:f(t,"name","n"),socialAccountIds:E(t,"accounts","account","a")??[],selectedPlatforms:E(t,"platforms","platform","p")??[],mediaUrls:E(t,"media")??[]},{workspace:!0});p(s,e);return}case"update":{let r=i[0];if(!r)throw new d("Usage: dun drafts update <id> --text ...",c.USAGE);let s={id:r},o=f(t,"text","content","c","t");if(o)s.content=o;let l=f(t,"name","n");if(l!==void 0)s.name=l;let u=E(t,"accounts","account","a");if(u)s.socialAccountIds=u;let m=E(t,"platforms","platform","p");if(m)s.selectedPlatforms=m;let g=E(t,"media");if(g)s.mediaUrls=g;let w=await a.patch(`/api/drafts/${r}`,s,{workspace:!0});p(w,e);return}case"delete":case"rm":{let r=i[0];if(!r)throw new d("Usage: dun drafts delete <id> [--yes]",c.USAGE);if(!e.yes&&!h(t,"yes","y"))throw new d("Refusing to delete without --yes",c.VALIDATION);let s=await a.delete(`/api/drafts/${r}`,void 0,{workspace:!0});p(s??{id:r,deleted:!0},e);return}default:throw new d(`Unknown drafts command "${n??""}". Try: list, get, create, update, delete`,c.USAGE)}}import{readFileSync as Qt,existsSync as en,statSync as tn}from"node:fs";import{basename as nn}from"node:path";var je=["image/jpeg","image/png","image/gif","image/webp"],Me=["video/mp4","video/quicktime","video/webm"];function ce(e){if(e<1024)return`${e}B`;if(e<1048576)return`${(e/1024).toFixed(1)}KB`;if(e<1073741824)return`${(e/1048576).toFixed(1)}MB`;let n=e/1073741824;return Number.isInteger(n)?`${n}GB`:`${n.toFixed(1)}GB`}function We(e){let{mimeType:n,fileSize:t}=e,i=je.includes(n),a=Me.includes(n);if(!i&&!a)return{ok:!1,message:`Unsupported MIME type "${n}". Allowed images: ${je.join(", ")}; videos: ${Me.join(", ")}`};if(a&&t>17179869184)return{ok:!1,message:`Video files must be less than ${ce(17179869184)} (got ${ce(t)}). Compress first, e.g.:
|
|
300
|
+
ffmpeg -i input.mp4 -vf scale=-2:1080 -c:v libx264 -crf 23 -c:a aac output.mp4`};if(i&&t>8388608)return{ok:!1,message:`Image files must be less than 8MB (got ${ce(t)}).`};return{ok:!0}}function rn(e){let n=e.split(".").pop()?.toLowerCase()??"";return{jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",mp4:"video/mp4",mov:"video/quicktime",webm:"video/webm"}[n]??"application/octet-stream"}async function qe(e,n,t,i){y(e),I(e);let a=b(e);switch(n){case"list":case"ls":{let r=await a.get("/api/media",{workspace:!0,query:{limit:U(t,"limit")??50,offset:U(t,"offset")??0}});p(r,{...e,emptyMessage:"No media yet. Upload with dun media upload <file>."});return}case"get":{let r=i[0];if(!r)throw new d("Usage: dun media get <id>",c.USAGE);let s=await a.get(`/api/media/${r}`,{workspace:!0});p(s,e);return}case"upload":{let r=i[0]||f(t,"file","f");if(!r)throw new d('Usage: dun media upload <file> [--alt "..."]',c.USAGE);if(!en(r))throw new d(`File not found: ${r}`,c.VALIDATION);let s=nn(r),o=tn(r).size,l=f(t,"mime","type","content-type")||rn(s),u=f(t,"alt","alt-text"),m=We({mimeType:l,fileSize:o});if(!m.ok)throw new d(m.message,c.VALIDATION);let g=await a.post("/api/media/upload-url",{filename:s,mimeType:l,fileSize:o},{workspace:!0});R(e.debug,`PUT ${g.uploadUrl}`,{storageKey:g.storageKey,fileSize:o});let w=Qt(r),k;try{k=await fetch(g.uploadUrl,{method:"PUT",headers:{"Content-Type":l,"Content-Length":String(o)},body:w})}catch(N){throw new d(`Upload PUT failed: ${N.message}`,c.NETWORK)}if(!k.ok){let N=await k.text().catch(()=>"");throw new d(`Upload PUT failed with HTTP ${k.status}${N?`: ${N.slice(0,200)}`:""}`,c.NETWORK)}let v=await a.post("/api/media/complete",{storageKey:g.storageKey,originalFilename:s,mimeType:l,fileSize:o,altText:u},{workspace:!0});p(v,e);return}case"delete":case"rm":{let r=i[0];if(!r)throw new d("Usage: dun media delete <id> [--yes]",c.USAGE);if(!e.yes&&!h(t,"yes","y"))throw new d("Refusing to delete without --yes",c.VALIDATION);let s=await a.delete(`/api/media/${r}`,void 0,{workspace:!0});p(s??{id:r,deleted:!0},e);return}default:throw new d(`Unknown media command "${n??""}". Try: list, get, upload, delete`,c.USAGE)}}async function Ve(e,n,t,i){y(e),I(e);let a=b(e),r=n[0];if(r==="collections"||r==="collection"){let s=n[1]||"list";switch(s){case"list":case"ls":{let o=await a.get("/api/memory/collections",{workspace:!0});p(o,{...e,emptyMessage:'No memory collections yet. Create one with dun memory collections create --name "..."'});return}case"create":{let o=f(t,"name","n");if(!o)throw new d("Missing --name",c.VALIDATION);let l=await a.post("/api/memory/collections",{name:o,color:f(t,"color")??"blue",isPrivate:h(t,"private")},{workspace:!0});p(l,e);return}case"get":{let o=n[2]||i[0];if(!o)throw new d("Usage: dun memory collections get <id>",c.USAGE);let l=await a.get(`/api/memory/collections/${o}`,{workspace:!0});p(l,e);return}case"update":{let o=n[2]||i[0];if(!o)throw new d("Usage: dun memory collections update <id> [--name] [--color] [--private|--public]",c.USAGE);let l=f(t,"name","n"),u=f(t,"color"),m=h(t,"private"),g=h(t,"public");if(m&&g)throw new d("Use either --private or --public, not both",c.VALIDATION);let w={};if(l)w.name=l;if(u)w.color=u;if(m)w.isPrivate=!0;if(g)w.isPrivate=!1;if(Object.keys(w).length===0)throw new d("Provide at least one of --name, --color, --private, or --public",c.VALIDATION);let k=await a.patch(`/api/memory/collections/${o}`,w,{workspace:!0});p(k,e);return}case"delete":case"rm":{let o=n[2]||i[0];if(!o)throw new d("Usage: dun memory collections delete <id> [--yes]",c.USAGE);if(!e.yes&&!h(t,"yes","y"))throw new d("Refusing to delete without --yes",c.VALIDATION);let l=await a.delete(`/api/memory/collections/${o}`,void 0,{workspace:!0});p(l??{id:o,deleted:!0},e);return}default:throw new d(`Unknown memory collections command "${s}". Try: list, create, get, update, delete`,c.USAGE)}}switch(r){case"list":case"ls":{let s=f(t,"collection","collection-id","c");if(!s)throw new d("Missing --collection <id>",c.VALIDATION);let o=await a.get("/api/memory",{workspace:!0,query:{collectionId:s,limit:U(t,"limit")??100}});p(o,{...e,emptyMessage:"No memories in this collection."});return}case"save":case"add":{let s=f(t,"collection","collection-id","c"),o=f(t,"text","t");if(!s)throw new d("Missing --collection <id>",c.VALIDATION);if(!o)throw new d("Missing --text",c.VALIDATION);let l=await a.post("/api/memory",{collectionId:s,text:o},{workspace:!0});p(l,e);return}case"search":{let s=f(t,"prompt","q","query","text"),o=E(t,"collections","collection","collection-ids","c")??[];if(!s)throw new d("Missing --prompt",c.VALIDATION);if(o.length===0)throw new d("Missing --collections <id,id>",c.VALIDATION);let l=await a.post("/api/memory/search",{prompt:s,collectionIds:o,topK:U(t,"top-k","topk","k")??5},{workspace:!0});p(l,e);return}case"delete":case"rm":{let s=n[1]||i[0],o=f(t,"collection","collection-id","c");if(!s)throw new d("Usage: dun memory delete <id> --collection <id>",c.USAGE);if(!o)throw new d("Missing --collection <id>",c.VALIDATION);if(!e.yes&&!h(t,"yes","y"))throw new d("Refusing to delete without --yes",c.VALIDATION);let l=await a.delete(`/api/memory/${s}`,{collectionId:o,memoryId:s},{workspace:!0});p(l??{id:s,deleted:!0},e);return}default:throw new d(`Unknown memory command "${r??""}". Try: collections, list, save, search, delete`,c.USAGE)}}import{readFileSync as on}from"node:fs";async function Be(e,n,t,i){y(e);let a=I(e),r=b(e),s=`/api/workspaces/${a}/ai-voice-settings`;switch(n){case"get":case"show":case void 0:{let o=await r.get(s,{workspace:!0});p(o??{personalization:null,hint:'No personalization set. Use dun personalization set --description "..."'},e);return}case"set":case"update":{let o=h(t,"clear"),l=f(t,"description","d"),u=f(t,"file","f");if([o,Boolean(l),Boolean(u)].filter(Boolean).length!==1)throw new d("Use exactly one of --description, --file, or --clear",c.VALIDATION);let g;if(o)g=null;else if(u){let k;try{k=on(u,"utf8").trim()}catch(v){throw new d(`Could not read file: ${v.message}`,c.VALIDATION)}if(!k)throw new d("File is empty",c.VALIDATION);g={description:k}}else{let k=l.trim();if(!k)throw new d("--description cannot be empty",c.VALIDATION);g={description:k}}let w=await r.patch(s,g,{workspace:!0});p(w,e);return}default:throw new d(`Unknown personalization command "${n}". Try: get, set`,c.USAGE)}}function Ge(e){return e.trim().replace(/^\/?r\//i,"").replace(/^@/,"")}async function de(e,n,t,i){y(e),I(e);let a=b(e);switch(n){case"flairs":case"flair":case"flair-options":{let r=i[0]||f(t,"subreddit","sub","r");if(!r)throw new d(`Usage: dun reddit flairs <subreddit>
|
|
301
|
+
Example: dun reddit flairs tamilyapping --json`,c.USAGE);let s=Ge(r),l=((await a.get("/api/social-accounts/reddit/flair-options",{workspace:!0,query:{subreddit:s}})).choices??[]).map((u)=>({id:u.id,text:u.text,textEditable:u.textEditable!==!1}));p({subreddit:s,choices:l},{...e,emptyMessage:`No flairs returned for r/${s}. The subreddit may not use post flair.`});return}case"requirements":case"post-requirements":{let r=i[0]||f(t,"subreddit","sub","r");if(!r)throw new d("Usage: dun reddit requirements <subreddit>",c.USAGE);let s=Ge(r),o=await a.get("/api/social-accounts/reddit/post-requirements",{workspace:!0,query:{subreddit:s}});p({subreddit:s,requirements:o},e);return}default:throw new d(`Unknown reddit command "${n??""}". Try: flairs, requirements`,c.USAGE)}}async function le(e,n,t,i){y(e),I(e);let a=b(e);switch(n){case"boards":case"list":case"list-boards":{let r=await a.get("/api/social-accounts/pinterest/boards",{workspace:!0});p({boards:r.boards??[]},{...e,emptyMessage:'No Pinterest boards on this account. Create one with dun pinterest create-board --name "..."'});return}case"create-board":case"board-create":case"create":{let r=f(t,"name","n")||i[0]||"";if(!r.trim())throw new d(`Usage: dun pinterest create-board --name "Brand assets" [--private]
|
|
302
|
+
Example: dun pinterest create-board --name "Landing page" --json`,c.USAGE);let s=await a.post("/api/social-accounts/pinterest/boards",{name:r.trim(),privacy:h(t,"private")?"SECRET":"PUBLIC"},{workspace:!0});p(s,e);return}default:throw new d(`Unknown pinterest command "${n??""}". Try: boards, create-board`,c.USAGE)}}async function ue(e,n,t,i){y(e),I(e);let a=b(e);switch(n){case"channels":case"list":case"list-channels":{let r=f(t,"account","a"),s=r?{socialAccountId:r}:void 0,o=await a.get("/api/social-accounts/discord/channels",{workspace:!0,query:s});p({channels:o.channels??[],guildId:o.guildId,source:o.source},{...e,emptyMessage:"No text or announcement channels. Reconnect Discord and pick a server the bot can post in."});return}default:throw new d(`Unknown discord command "${n??""}". Try: channels
|
|
303
|
+
Example: dun discord channels --account <id>`,c.USAGE)}}import{existsSync as He}from"node:fs";import{mkdir as ze,writeFile as Je,readFile as fe}from"node:fs/promises";import{dirname as ge,join as A}from"node:path";import{homedir as F}from"node:os";import{fileURLToPath as sn}from"node:url";var _=[{id:"claude",label:"Claude Code",aliases:["claude-code"],dest:(e)=>A(e,".claude","skills","dunsocial","SKILL.md")},{id:"codex",label:"Codex",dest:(e)=>A(e,".codex","skills","dunsocial","SKILL.md"),afterInstall:async(e)=>({agentsPath:await cn(e)})},{id:"cursor",label:"Cursor",dest:(e)=>A(e,".cursor","skills","dunsocial","SKILL.md")},{id:"pi",label:"Pi",dest:(e)=>A(e,".pi","agent","skills","dunsocial","SKILL.md")},{id:"opencode",label:"OpenCode",dest:(e)=>A(e,".config","opencode","skills","dunsocial","SKILL.md")},{id:"agents",label:"Universal (.agents)",aliases:["universal"],dest:(e)=>A(e,".agents","skills","dunsocial","SKILL.md")},{id:"copilot",label:"GitHub Copilot",aliases:["github-copilot","github"],dest:(e)=>A(e,".copilot","skills","dunsocial","SKILL.md")},{id:"windsurf",label:"Windsurf",dest:(e)=>A(e,".codeium","windsurf","skills","dunsocial","SKILL.md")},{id:"goose",label:"Goose",dest:(e)=>A(e,".config","goose","skills","dunsocial","SKILL.md")},{id:"gemini",label:"Gemini CLI",aliases:["gemini-cli"],dest:(e)=>A(e,".gemini","skills","dunsocial","SKILL.md")},{id:"amp",label:"Amp",dest:(e)=>A(e,".config","agents","skills","dunsocial","SKILL.md")},{id:"grok",label:"Grok Build",dest:(e)=>A(e,".grok","skills","dunsocial","SKILL.md")},{id:"continue",label:"Continue",dest:(e)=>A(e,".continue","skills","dunsocial","SKILL.md")},{id:"antigravity",label:"Antigravity",dest:(e)=>A(e,".gemini","antigravity","skills","dunsocial","SKILL.md")}],pe=new Map;for(let e of _){pe.set(e.id,e);for(let n of e.aliases??[])pe.set(n,e)}var Fe=`Usage: dun skill install <${_.map((e)=>e.id).join("|")}|all>`;function me(){let e=ge(sn(import.meta.url)),n=A("skills","dunsocial","SKILL.md"),t=[A(e,"..",n),A(e,"..","..",n)];return t.find(He)??t[0]}function an(){let e=F();return _.map((n)=>({id:n.id,label:n.label,path:n.dest(e),aliases:n.aliases??[]}))}async function Ye(){try{return await fe(me(),"utf8")}catch{throw new d(`Bundled skill not found at ${me()}. Reinstall dunsocial.`,c.USAGE)}}async function Xe(e,n){await ze(ge(e),{recursive:!0}),await Je(e,n,"utf8")}async function cn(e){let n=A(F(),".codex","AGENTS.md");await ze(ge(n),{recursive:!0});let t="";try{t=await fe(n,"utf8")}catch{t=""}let i=["<!-- BEGIN DUNSOCIAL CLI SKILL -->","## DunSocial CLI","","Use the `dun` CLI for DunSocial scheduling from the shell. Prefer `--json` output.",`Skill details: ${e}`,"","Common flow:","1. `dun auth status --json`","2. `dun workspace list --json` / `dun workspace use <id>`","3. `dun accounts list --json`",'4. `dun posts schedule --text "..." --accounts <id> --in 1h --json`',"<!-- END DUNSOCIAL CLI SKILL -->"].join(`
|
|
304
|
+
`),a="<!-- BEGIN DUNSOCIAL CLI SKILL -->",r="<!-- END DUNSOCIAL CLI SKILL -->",s,o=t.indexOf(a),l=t.indexOf(r);if(o!==-1&&l!==-1&&l>o)s=t.slice(0,o)+i+t.slice(l+r.length);else if(t.trim())s=`${t.trimEnd()}
|
|
305
|
+
|
|
306
|
+
${i}
|
|
307
|
+
`;else s=`${i}
|
|
308
|
+
`;return await Je(n,s,"utf8"),n}async function K(e){let n=e?.home??F(),t=e?.markdown;if(t===void 0)try{t=await Ye()}catch{return[]}let i=[];for(let a of _){let r=a.dest(n);if(!He(r))continue;let s="";try{s=await fe(r,"utf8")}catch{continue}if(s===t){i.push({target:a.id,path:r,updated:!1});continue}if(await Xe(r,t),a.afterInstall)try{await a.afterInstall(r)}catch{}i.push({target:a.id,path:r,updated:!0})}return i}async function Ke(e,n){let t=e.dest(F());await Xe(t,n);let i=e.afterInstall?await e.afterInstall(t):{};return{target:e.id,path:t,message:`Installed DunSocial skill for ${e.label}. Restart or open a new session if needed.`,...i}}async function Ze(e,n,t,i){switch(n){case"path":{p({path:me()},e);return}case"list":{p({targets:an(),hint:"Install with: dun skill install <target|all>"},e);return}case"install":{let a=(i[0]||"").toLowerCase();if(!a)throw new d(Fe,c.USAGE);let r=await Ye();if(a==="all"){let l=[];for(let u of _){let m=await Ke(u,r);l.push({target:m.target,path:m.path})}p({target:"all",count:l.length,installed:l,message:`Installed DunSocial skill for ${l.length} agents.`},e);return}let s=pe.get(a);if(!s)throw new d(`Unknown agent "${a}". ${Fe}
|
|
309
|
+
Try: dun skill list`,c.USAGE);let o=await Ke(s,r);p(o,e);return}default:throw new d(`Unknown skill command "${n??""}". Try: install, list, path`,c.USAGE)}}import{spawn as dn}from"node:child_process";import{realpathSync as ln}from"node:fs";var S="dunsocial",un=`https://registry.npmjs.org/${S}/latest`,pn=400;function j(e=process.argv[1]??""){let n=e.replaceAll("\\","/");try{n=ln(e).replaceAll("\\","/")}catch{}if(n.includes("/.npm/_npx")||n.includes("/npm/_npx")||n.includes("/_npx/"))return{packageManager:"npx",manual:`npm install -g ${S}@latest`};if(n.includes("/.pnpm/")||n.includes("/pnpm/"))return{packageManager:"pnpm",command:"pnpm",args:["add","-g",`${S}@latest`],manual:`pnpm add -g ${S}@latest`};if(n.includes("/.yarn/")||n.includes("/yarn/global"))return{packageManager:"yarn",command:"yarn",args:["global","add",`${S}@latest`],manual:`yarn global add ${S}@latest`};if(n.includes("/.bun/")||n.includes("/bun/install/global"))return{packageManager:"bun",command:"bun",args:["add","-g",`${S}@latest`],manual:`bun add -g ${S}@latest`};if(n.includes(`/node_modules/${S}/`))return{packageManager:"npm",command:"npm",args:["install","-g",`${S}@latest`],manual:`npm install -g ${S}@latest`};return{packageManager:"unknown",manual:`npm install -g ${S}@latest`}}function M(e,n){let t=e.replace(/^v/,"").split(".").map((r)=>Number.parseInt(r,10)||0),i=n.replace(/^v/,"").split(".").map((r)=>Number.parseInt(r,10)||0),a=Math.max(t.length,i.length);for(let r=0;r<a;r++){let s=t[r]??0,o=i[r]??0;if(s>o)return 1;if(s<o)return-1}return 0}function Qe(e){return Number.parseInt(e.replace(/^v/,"").split(".")[0]??"0",10)||0}function et(e,n){return Qe(n)>Qe(e)}async function H(e=fetch,n){let t=await e(un,{headers:{Accept:"application/json"},...n});if(!t.ok)throw new d(`Could not check npm for updates (HTTP ${t.status}). Try: npm install -g ${S}@latest`,c.NETWORK);let i=await t.json();if(!i.version)throw new d("npm registry response missing version",c.NETWORK);return i.version}async function we(e=pn,n=fetch){let t=new AbortController,i=setTimeout(()=>t.abort(),e);try{return await H(n,{signal:t.signal})}catch{return}finally{clearTimeout(i)}}function z(e,n,t){let i=t?.stdio??"inherit";return new Promise((a,r)=>{let s=dn(e,n,{stdio:i,env:process.env,windowsHide:!0,shell:process.platform==="win32"});s.on("error",(o)=>{r(new d(`Failed to run ${e}: ${o.message}. Try manually: ${e} ${n.join(" ")}`,c.NETWORK))}),s.on("close",(o)=>a(o??1))})}async function tt(e,n,t){let i=h(t,"check"),a=h(t,"force"),r=T,s=j(),o;try{o=await H()}catch(g){if(g instanceof d)throw g;throw new d(`Could not check npm for updates: ${g.message}`,c.NETWORK)}let l=M(o,r)>0,u={current:r,latest:o,upToDate:!l,packageManager:s.packageManager,command:s.manual};if(!l&&!a){p(e.json?u:`Already on the latest version (${r}).`,e);return}if(i){p(e.json?{...u,updateAvailable:l}:`Update available: ${r} → ${o}
|
|
254
310
|
Run: dun update
|
|
255
|
-
Or: ${s.manual}`,e);return}if(!s.command||!s.args){
|
|
311
|
+
Or: ${s.manual}`,e);return}if(!s.command||!s.args){p(e.json?{...u,applied:!1,hint:s.manual}:`Update available: ${r} → ${o}
|
|
256
312
|
Cannot auto-update this install (${s.packageManager}).
|
|
257
|
-
Run: ${s.manual}`,e);return}if(!e.json&&!e.quiet)process.stderr.write(`Updating ${
|
|
258
|
-
`);let
|
|
259
|
-
|
|
260
|
-
`);
|
|
261
|
-
`)}
|
|
262
|
-
`)}async function
|
|
263
|
-
|
|
264
|
-
`);
|
|
265
|
-
`)
|
|
313
|
+
Run: ${s.manual}`,e);return}if(!e.json&&!e.quiet)process.stderr.write(`Updating ${S}: ${r} → ${o} via ${s.packageManager}...
|
|
314
|
+
`);let m=await z(s.command,s.args);if(m!==0)throw new d(`Update command failed (exit ${m}). Try: ${s.manual}`,c.NETWORK);try{await K()}catch{}p(e.json?{...u,applied:!0}:`Updated via ${s.packageManager}. Run dun --version to confirm.`,e)}import{mkdir as mn,readFile as fn,writeFile as gn}from"node:fs/promises";import{dirname as wn,join as nt}from"node:path";import{homedir as hn}from"node:os";var rt=86400000,kn=new Set(["update","upgrade","self-update"]);function Y(e=process.env){if(e.DUN_UPDATE_CACHE)return e.DUN_UPDATE_CACHE;let n=e.XDG_CACHE_HOME,t=n?n:nt(hn(),".cache");return nt(t,"dunsocial","update.json")}function bn(e){let n=(e??"").trim().toLowerCase();return n==="0"||n==="never"||n==="off"||n==="false"||n==="no"}function yn(e=process.env){return e.CI==="true"||e.CI==="1"||Boolean(e.GITHUB_ACTIONS)||Boolean(e.TF_BUILD)||Boolean(e.GITLAB_CI)||Boolean(e.CIRCLECI)||Boolean(e.BUILDKITE)}function In(e){return Boolean(e&&kn.has(e))}function An(e){return Boolean(e.command&&e.args)}function it(e){let n=e.env??process.env;if(h(e.flags,"no-update"))return!1;if(h(e.flags,"json","j"))return!1;if(In(e.group))return!1;if(!e.stdoutIsTty)return!1;if(n.DUN_JSON==="1")return!1;if(yn(n))return!1;if(bn(n.DUN_UPDATE))return!1;return!0}function ot(e,n=Date.now(),t=rt){if(!e.lastCheckedAt||!e.latest)return!1;let i=Date.parse(e.lastCheckedAt);if(!Number.isFinite(i))return!1;return n-i<t}function Sn(e,n,t=Date.now(),i=rt){if(e.lastFailedVersion!==n||!e.lastFailedAt)return!0;let a=Date.parse(e.lastFailedAt);if(!Number.isFinite(a))return!0;return t-a>=i}async function st(e=Y()){try{let n=await fn(e,"utf8");return JSON.parse(n)}catch{return{}}}async function J(e,n=Y()){return await mn(wn(n),{recursive:!0}),await gn(n,`${JSON.stringify(e,null,2)}
|
|
315
|
+
`,"utf8"),e}async function at(e){let n=e?.cachePath??Y(),t=e?.now??Date.now(),i=await st(n);if(ot(i,t))return i;let a=await(e?.fetchLatest??we)();if(!a)return i;return J({...i,lastCheckedAt:new Date(t).toISOString(),latest:a},n)}async function Cn(e){let n=e.write??((o)=>process.stderr.write(o)),t=e.cachePath??Y(),i=e.now??Date.now(),a=e.install??j();if(!An(a))return{applied:!1,reason:"no-install-command"};let r=await e.checkPromise??await st(t),s=r.latest;if(!s||!ot(r,i)){let o=await(e.fetchLatest??we)();if(o)r=await J({...r,lastCheckedAt:new Date(i).toISOString(),latest:o},t),s=o}if(!s)return{applied:!1,reason:"no-latest"};if(M(s,e.current)<=0)return{applied:!1,reason:"up-to-date",latest:s};if(et(e.current,s)){if(!e.quiet)n(`Major update available: ${e.current} -> ${s}. Run: dun update --yes
|
|
316
|
+
`);return{applied:!1,reason:"major",latest:s}}if(!Sn(r,s,i))return{applied:!1,reason:"failed-backoff",latest:s};try{let o=await(e.spawn??z)(a.command,a.args,{stdio:"ignore"});if(o!==0)throw Error(`exit ${o}`);if(await J({...r,latest:s,lastAppliedAt:new Date(i).toISOString(),lastFailedAt:void 0,lastFailedVersion:void 0},t),!e.quiet)n(`Updated ${S} ${e.current} → ${s}
|
|
317
|
+
`);return{applied:!0,reason:"applied",latest:s}}catch{if(await J({...r,latest:s,lastFailedAt:new Date(i).toISOString(),lastFailedVersion:s},t),!e.quiet)n(`Auto-update failed. Try: ${a.manual}
|
|
318
|
+
`);return{applied:!1,reason:"apply-failed",latest:s}}}async function ct(e){try{await e.refreshSkills();let t=await(e.apply??Cn)({current:e.current,checkPromise:e.checkPromise,quiet:e.quiet,install:e.install,cachePath:e.cachePath,now:e.now,fetchLatest:e.fetchLatest,spawn:e.spawn,write:e.write});if(t.applied)await e.refreshSkills();return t}catch{return}}function dt(){if(x(process.stdout))process.stdout.write(`${Ue()}
|
|
319
|
+
|
|
320
|
+
`);process.stdout.write(`${re}
|
|
321
|
+
`)}async function Tn(){let e=await Z(),n=process.env.DUN_TOKEN||e.token,t=process.env.DUN_WORKSPACE_ID||e.defaultWorkspaceId;process.stdout.write(`${Ne({token:n,workspaceId:t,config:e})}
|
|
322
|
+
`)}async function Un(e){let n=Q(e),{group:t,action:i,nest:a,flags:r,positionals:s}=n;if(te(r)&&!t)return process.stdout.write(`${T}
|
|
323
|
+
`),c.OK;if(t==="help"||!t&&ee(r)){let l=t==="help"?i||s[0]:void 0;if(l)process.stdout.write(`${ie(l)}
|
|
324
|
+
`);else dt();return c.OK}if(!t){if(x(process.stdout))await Tn();else dt();return c.OK}if(ee(r))return process.stdout.write(`${ie(t)}
|
|
325
|
+
`),c.OK;let o=await Ie({apiUrl:f(r,"api-url","apiUrl"),token:f(r,"token"),workspace:f(r,"workspace","w"),json:h(r,"json","j")?!0:void 0,debug:h(r,"debug"),quiet:h(r,"quiet","q"),yes:h(r,"yes","y")});if(te(r))return p({version:T},o),c.OK;try{switch(t){case"auth":await xe(o,i,r,s);break;case"workspace":case"workspaces":await Le(o,i,r,s);break;case"account":case"accounts":if(i==="reddit"){await de(o,s[0],r,s.slice(1));break}if(i==="pinterest"){await le(o,s[0],r,s.slice(1));break}if(i==="discord"){await ue(o,s[0],r,s.slice(1));break}await $e(o,i??"list",r,s);break;case"reddit":await de(o,i,r,s);break;case"pinterest":await le(o,i,r,s);break;case"discord":await ue(o,i,r,s);break;case"post":case"posts":await Pe(o,i,r,s);break;case"draft":case"drafts":await _e(o,i,r,s);break;case"media":await qe(o,i,r,s);break;case"memory":{let l=a?[a,i].filter(Boolean):i?[i]:[];await Ve(o,l,r,s);break}case"personalization":case"voice":await Be(o,i,r,s);break;case"skill":case"skills":await Ze(o,i,r,s);break;case"update":case"upgrade":case"self-update":await tt(o,i,r);break;case"version":p({version:T},o);break;default:throw new d(`Unknown command "${t}". Run dun --help`,c.USAGE)}return c.OK}catch(l){return Se(l,o)}}var lt=process.argv.slice(2),he=Q(lt),ut=it({flags:he.flags,group:he.group,stdoutIsTty:Boolean(process.stdout.isTTY),env:process.env}),En=ut?at():Promise.resolve(void 0),pt=await Un(lt);if(pt===c.OK&&ut)await ct({current:T,checkPromise:En,quiet:h(he.flags,"quiet","q"),refreshSkills:K});process.exit(pt);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dunsocial
|
|
3
|
-
description: Schedule, publish, and manage DunSocial posts, drafts, media, memory, and workspace personalization (AI voice) from the shell via the dun CLI (npm package dunsocial). Use when the user wants to post or schedule to X/LinkedIn/Bluesky/Threads/Reddit/Pinterest/Instagram/YouTube through DunSocial, manage drafts or media uploads, search workspace memory, get/set AI voice personalization, or wire social publishing into CI/agent workflows.
|
|
3
|
+
description: Schedule, publish, and manage DunSocial posts, drafts, media, memory, and workspace personalization (AI voice) from the shell via the dun CLI (npm package dunsocial). Use when the user wants to post or schedule to X/LinkedIn/Bluesky/Threads/Reddit/Pinterest/Instagram/YouTube/Discord through DunSocial, manage drafts or media uploads, search workspace memory, get/set AI voice personalization, or wire social publishing into CI/agent workflows.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# DunSocial CLI skill
|
|
@@ -10,11 +10,13 @@ Use the **`dun`** binary from the **`dunsocial`** npm package.
|
|
|
10
10
|
```bash
|
|
11
11
|
npm install -g dunsocial
|
|
12
12
|
# or
|
|
13
|
-
npx dunsocial …
|
|
13
|
+
npx --yes dunsocial@latest …
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
Prefer **`--json`** on every command so output is machine-parseable.
|
|
17
17
|
|
|
18
|
+
Do **not** rely on global auto-update. That path is for human TTY installs only and never runs under `--json`, `CI`, or `npx`. Prefer `npx --yes dunsocial@latest` (or a pinned `dunsocial@x.y.z`) in agents and CI. `DUN_UPDATE=0` or `--no-update` disables auto-update.
|
|
19
|
+
|
|
18
20
|
Remote hosts (ChatGPT web, Claude web) should keep using DunSocial **MCP**. This CLI is for **local agents, terminals, and CI**.
|
|
19
21
|
|
|
20
22
|
## Auth + workspace
|
|
@@ -85,15 +87,15 @@ dun media upload ./shot.png --alt "Product screenshot" --json
|
|
|
85
87
|
dun posts schedule --text "…" --accounts acc_x --media <assetId> --in 30m --json
|
|
86
88
|
```
|
|
87
89
|
|
|
88
|
-
Local preflight rejects images > 8MB and video >
|
|
90
|
+
Local preflight rejects images > 8MB and video > 16GB **before** upload-url. X publish then re-checks the connected account (default 20 min / 8 GB; Premium and Premium+ 125 min / 16 GB). Compress video first if needed:
|
|
89
91
|
|
|
90
92
|
```bash
|
|
91
93
|
ffmpeg -i input.mp4 -vf scale=-2:1080 -c:v libx264 -crf 23 -c:a aac output.mp4
|
|
92
94
|
```
|
|
93
95
|
|
|
94
|
-
##
|
|
96
|
+
## LinkedIn video
|
|
95
97
|
|
|
96
|
-
No `--meta`. Upload one MP4/MOV, attach with `--media`, target a
|
|
98
|
+
No `--meta`. Upload one MP4/MOV, attach with `--media`, target a personal LinkedIn or Company Page account (`dun accounts list --platform linkedin` or `--platform linkedin-business`). Rules: **exactly one video** or images — never mix.
|
|
97
99
|
|
|
98
100
|
```bash
|
|
99
101
|
dun accounts list --platform linkedin --json
|
|
@@ -106,6 +108,16 @@ dun posts publish \
|
|
|
106
108
|
--json
|
|
107
109
|
```
|
|
108
110
|
|
|
111
|
+
## YouTube Shorts only
|
|
112
|
+
|
|
113
|
+
DunSocial does **not** publish regular YouTube videos. Attach exactly one vertical or square video (3 minutes or less). The asset must include width, height, and duration or the API rejects it (landscape/unknown dimensions never fall back to a watch-page upload).
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
dun accounts list --platform youtube --json
|
|
117
|
+
dun media upload ./short.mp4 --json
|
|
118
|
+
dun posts publish --accounts <youtubeAccountId> --text "Title"$'\n\n'"Description" --media <assetId> --wait --json
|
|
119
|
+
```
|
|
120
|
+
|
|
109
121
|
## Reddit (special)
|
|
110
122
|
|
|
111
123
|
Reddit needs platform metadata under `--meta`. Discover flairs first — many subs require a **template id**, not free text.
|
|
@@ -158,6 +170,31 @@ dun posts publish \
|
|
|
158
170
|
|
|
159
171
|
Also: `dun reddit requirements <subreddit> --json` for live rules (`is_flair_required`, title limits).
|
|
160
172
|
|
|
173
|
+
## Discord (channel required)
|
|
174
|
+
|
|
175
|
+
Discord posts need `metadata.discord.channelId` (a snowflake from the connected server). One Discord server per workspace.
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
dun accounts list --platform discord --json
|
|
179
|
+
dun discord channels --json
|
|
180
|
+
# -> { id, name, type }
|
|
181
|
+
|
|
182
|
+
dun posts validate \
|
|
183
|
+
--accounts <discordAccountId> \
|
|
184
|
+
--text "Shipped from the CLI" \
|
|
185
|
+
--meta '{"discord":{"channelId":"<id>"}}' \
|
|
186
|
+
--json
|
|
187
|
+
|
|
188
|
+
dun posts publish \
|
|
189
|
+
--accounts <discordAccountId> \
|
|
190
|
+
--text "Shipped from the CLI" \
|
|
191
|
+
--meta '{"discord":{"channelId":"<id>"}}' \
|
|
192
|
+
--wait \
|
|
193
|
+
--json
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Text up to 2000 characters, up to 10 attachments. Mentions like `@everyone` are stripped on publish.
|
|
197
|
+
|
|
161
198
|
## Drafts / memory / threads
|
|
162
199
|
|
|
163
200
|
```bash
|
|
@@ -221,6 +258,7 @@ Failure JSON:
|
|
|
221
258
|
- `--accounts` = DunSocial social account **ids**, not handles
|
|
222
259
|
- Upload local files with `media upload` before attaching
|
|
223
260
|
- Reddit (and some other platforms) need `--meta '{"reddit":{…}}'`
|
|
261
|
+
- Discord needs `--meta '{"discord":{"channelId":"…"}}'`. Discover ids with `dun discord channels --json`.
|
|
224
262
|
- `posts get` / list never include OAuth secrets — safe to log
|
|
225
263
|
- MCP OAuth and CLI tokens are different systems
|
|
226
264
|
|
|
@@ -230,4 +268,5 @@ Failure JSON:
|
|
|
230
268
|
dun --help
|
|
231
269
|
dun posts --help
|
|
232
270
|
dun reddit --help
|
|
271
|
+
dun discord --help
|
|
233
272
|
```
|