dunsocial 0.12.0 → 0.14.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 +24 -5
- package/dist/index.js +118 -75
- package/package.json +2 -2
- package/skills/dunsocial/SKILL.md +20 -4
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,18 +95,35 @@ 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)
|
|
105
124
|
dun reddit flairs tamilyapping --json
|
|
106
125
|
|
|
107
|
-
# 3. Optional dry-run
|
|
126
|
+
# 3. Optional dry-run (API character/metadata checks, then live Reddit rules)
|
|
108
127
|
dun posts validate \
|
|
109
128
|
--accounts REDDIT_ACCOUNT_ID \
|
|
110
129
|
--media ASSET_ID \
|
|
@@ -165,7 +184,7 @@ dun auth status
|
|
|
165
184
|
dun posts list
|
|
166
185
|
dun media upload ./photo.png
|
|
167
186
|
dun reddit flairs some-subreddit
|
|
168
|
-
dun posts validate --accounts ACCOUNT_ID --
|
|
187
|
+
dun posts validate --accounts ACCOUNT_ID --text "Hello" --json
|
|
169
188
|
dun drafts create --text "Idea for later"
|
|
170
189
|
dun posts schedule-thread --account ACCOUNT_ID --file ./thread.json --in 2h
|
|
171
190
|
dun memory collections update COLLECTION_ID --name "Renamed"
|
package/dist/index.js
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{readFileSync as
|
|
3
|
-
`,"utf8");try{await
|
|
2
|
+
import{readFileSync as mt}from"node:fs";import{mkdir as ft,readFile as gt,writeFile as wt,chmod as ht,unlink as kt}from"node:fs/promises";import{dirname as bt,join as X}from"node:path";import{homedir as yt}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 he(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 It="https://api.dunsocial.com",ke="0.14.0";function At(){if(!ke.includes("@@"))return ke;try{let e=JSON.parse(mt(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 U=At();function W(){if(process.env.DUN_CONFIG)return process.env.DUN_CONFIG;let e=process.env.XDG_CONFIG_HOME,n=e?e:X(yt(),".config");return X(n,"dunsocial","config.json")}function P(){return W()}async function Z(){let e=W();try{let n=await gt(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 D(e){let n=W();await ft(bt(n),{recursive:!0});let t={...e,updatedAt:new Date().toISOString()};await wt(n,`${JSON.stringify(t,null,2)}
|
|
3
|
+
`,"utf8");try{await ht(n,384)}catch{}}async function be(){let e=W();try{await kt(e)}catch(n){if(n.code!=="ENOENT")throw new d(`Failed to clear config: ${n.message}`,c.USAGE)}}async function ye(e){let n=await Z(),t=(e.apiUrl||process.env.DUN_API_URL||n.apiUrl||It).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 p=u.indexOf("=");if(p!==-1){n[u.slice(2,p)]=u.slice(p+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 p=u.slice(1);if(p.length>1&&!p.includes("=")){for(let k of p)n[k]=!0;i+=1;continue}let g=p,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 C(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 m(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
|
|
8
|
-
`);else
|
|
9
|
-
`)
|
|
10
|
-
`);
|
|
11
|
-
`)}if(e
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
`)
|
|
15
|
-
|
|
6
|
+
`);return}process.stdout.write(`${Ct(e,n.emptyMessage)}
|
|
7
|
+
`)}function Ae(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:St(i),message:t,details:a??null}},null,2)}
|
|
8
|
+
`);else{process.stderr.write(`error: ${t}
|
|
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 St(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 Ct(e,n){if(Array.isArray(e)){if(e.length===0)return n??"(empty)";if(e.every((t)=>t&&typeof t==="object"))return Ie(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=Ie(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 Ie(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 p of Object.keys(u))i.add(p);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((p)=>Et(ne(u[p]),p==="content"||p==="text"?48:28))),s=a.map((u,p)=>Math.max(u.length,...r.map((g)=>g[p]?.length??0))),o=a.map((u,p)=>u.padEnd(s[p])).join(" "),l=r.map((u)=>u.map((p,g)=>p.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 Et(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)}
|
|
16
17
|
`);return}process.stderr.write(`[debug] ${n}
|
|
17
|
-
`)}function
|
|
18
|
-
${
|
|
18
|
+
`)}function T(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
|
|
19
20
|
|
|
20
21
|
Usage:
|
|
21
22
|
dun <command> [flags]
|
|
22
23
|
|
|
23
|
-
${
|
|
24
|
+
${T("\uD83D\uDD10","Auth")}
|
|
24
25
|
dun auth login # device-code browser flow
|
|
25
26
|
dun auth login --token <token> # CI / headless
|
|
26
27
|
dun auth login --no-browser
|
|
@@ -28,26 +29,31 @@ ${S("\uD83D\uDD10","Auth")}
|
|
|
28
29
|
dun auth whoami
|
|
29
30
|
dun auth status
|
|
30
31
|
|
|
31
|
-
${
|
|
32
|
+
${T("\uD83D\uDCC1","Workspaces")}
|
|
32
33
|
dun workspace list
|
|
33
34
|
dun workspace use <id|slug>
|
|
34
35
|
dun workspace current
|
|
35
36
|
|
|
36
|
-
${
|
|
37
|
+
${T("\uD83D\uDD17","Accounts")}
|
|
37
38
|
dun accounts list [--platform x|reddit|...]
|
|
38
39
|
|
|
39
|
-
${
|
|
40
|
+
${T("\uD83D\uDFE0","Reddit")}
|
|
40
41
|
dun reddit flairs <subreddit> # template id + text + textEditable
|
|
41
42
|
dun reddit requirements <subreddit> # live post rules (flair required, ...)
|
|
42
43
|
# alias: dun accounts reddit flairs <subreddit>
|
|
43
44
|
|
|
44
|
-
${
|
|
45
|
+
${T("\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
|
+
${T("\uD83D\uDCDD","Posts")}
|
|
45
51
|
dun posts list [--status scheduled|published|failed|draft|cancelled]
|
|
46
52
|
dun posts get <id>
|
|
47
|
-
dun posts validate --accounts <id> [--media id] --meta '{...}'
|
|
53
|
+
dun posts validate --accounts <id> --text "..." [--media id] [--meta '{...}']
|
|
48
54
|
dun posts schedule --text "..." --accounts <id,id> (--at ISO | --in 2h) [--media id,id] [--meta '{...}']
|
|
49
55
|
dun posts publish --text "..." --accounts <id,id> [--media id,id] [--meta '{...}'] [--natural] [--wait]
|
|
50
|
-
dun posts reschedule <id> --at ISO|--in 2h [--text "..."]
|
|
56
|
+
dun posts reschedule <id> --at ISO|--in 2h [--text "..."] [--media id] [--meta '{...}']
|
|
51
57
|
dun posts cancel <id>
|
|
52
58
|
dun posts delete <id> [--yes]
|
|
53
59
|
dun posts x-cap
|
|
@@ -58,24 +64,24 @@ ${S("\uD83D\uDCDD","Posts")}
|
|
|
58
64
|
# Reddit --meta example (video):
|
|
59
65
|
# --meta '{"reddit":{"subreddit":"tamilyapping","kind":"video","title":"...","flairId":"..."}}'
|
|
60
66
|
# kinds: self | link | image | video
|
|
61
|
-
#
|
|
62
|
-
#
|
|
67
|
+
# LinkedIn video (no --meta): upload one mp4/mov, then --media ASSET on a LinkedIn or Company Page account
|
|
68
|
+
# Do not mix video + images on LinkedIn.
|
|
63
69
|
# publish queues async work; without --wait, status scheduled means queued
|
|
64
70
|
|
|
65
|
-
${
|
|
71
|
+
${T("\uD83D\uDCC4","Drafts")}
|
|
66
72
|
dun drafts list
|
|
67
73
|
dun drafts get <id>
|
|
68
74
|
dun drafts create --text "..." [--platforms x,linkedin] [--accounts id,id]
|
|
69
75
|
dun drafts update <id> --text "..."
|
|
70
76
|
dun drafts delete <id> [--yes]
|
|
71
77
|
|
|
72
|
-
${
|
|
78
|
+
${T("\uD83D\uDDBC️","Media")}
|
|
73
79
|
dun media list
|
|
74
80
|
dun media get <id>
|
|
75
|
-
dun media upload <file> [--alt "..."] # local preflight: images <=8MB, video <=
|
|
81
|
+
dun media upload <file> [--alt "..."] # local preflight: images <=8MB, video <=16GB
|
|
76
82
|
dun media delete <id> [--yes]
|
|
77
83
|
|
|
78
|
-
${
|
|
84
|
+
${T("\uD83E\uDDE0","Memory")}
|
|
79
85
|
dun memory collections list
|
|
80
86
|
dun memory collections create --name "..." [--color blue] [--private]
|
|
81
87
|
dun memory collections get <id>
|
|
@@ -86,7 +92,7 @@ ${S("\uD83E\uDDE0","Memory")}
|
|
|
86
92
|
dun memory search --prompt "..." --collections <id,id> [--top-k 5]
|
|
87
93
|
dun memory delete <id> --collection <id> [--yes]
|
|
88
94
|
|
|
89
|
-
${
|
|
95
|
+
${T("\uD83C\uDF99️","Personalization")}
|
|
90
96
|
# workspace AI voice
|
|
91
97
|
dun personalization get
|
|
92
98
|
dun personalization set --description "..."
|
|
@@ -94,21 +100,24 @@ ${S("\uD83C\uDF99️","Personalization")}
|
|
|
94
100
|
dun personalization set --clear
|
|
95
101
|
# requires PAT scope workspace:write for set (owner/admin)
|
|
96
102
|
|
|
97
|
-
${
|
|
103
|
+
${T("\uD83E\uDDE9","Agent skills")}
|
|
98
104
|
dun skill install <agent|all>
|
|
99
105
|
dun skill list
|
|
100
106
|
dun skill path
|
|
101
107
|
# agents: claude codex cursor pi opencode agents copilot windsurf
|
|
102
108
|
# goose gemini amp grok continue antigravity
|
|
103
109
|
|
|
104
|
-
${
|
|
110
|
+
${T("⬆️","Update")}
|
|
105
111
|
dun update # upgrade to latest from npm
|
|
106
112
|
dun update --check # report only
|
|
107
113
|
dun update --force # reinstall latest even if current
|
|
108
114
|
# aliases: upgrade, self-update
|
|
109
115
|
# detects npm / bun / pnpm / yarn; prints command for npx
|
|
116
|
+
# TTY + global install: auto-upgrades after a successful command (not agents/CI/npx)
|
|
117
|
+
# disable: --no-update or DUN_UPDATE=0
|
|
118
|
+
# major bumps print: dun update --yes
|
|
110
119
|
|
|
111
|
-
${
|
|
120
|
+
${T("⚙️","Global flags")}
|
|
112
121
|
--json Machine-readable output (default when non-TTY / CI)
|
|
113
122
|
--workspace <id> Override workspace for this command
|
|
114
123
|
--api-url <url> Override API base (default https://api.dunsocial.com)
|
|
@@ -116,17 +125,18 @@ ${S("⚙️","Global flags")}
|
|
|
116
125
|
--debug Log HTTP method/path/status to stderr
|
|
117
126
|
--quiet Suppress human success output
|
|
118
127
|
--yes Skip destructive confirmations
|
|
128
|
+
--no-update Skip silent auto-update for this command
|
|
119
129
|
--help, -h Show help
|
|
120
130
|
--version, -v Show version
|
|
121
131
|
|
|
122
|
-
${
|
|
123
|
-
DUN_TOKEN DUN_WORKSPACE_ID DUN_API_URL DUN_CONFIG DUN_JSON=1 DUN_DEBUG=1
|
|
132
|
+
${T("\uD83C\uDF31","Env")}
|
|
133
|
+
DUN_TOKEN DUN_WORKSPACE_ID DUN_API_URL DUN_CONFIG DUN_JSON=1 DUN_DEBUG=1 DUN_UPDATE=0
|
|
124
134
|
|
|
125
|
-
${
|
|
126
|
-
0 ok | 1 usage | 2 auth | 3 forbidden | 4 validation | 5 not found | 6 rate limit | 7 network
|
|
135
|
+
${T("\uD83D\uDEAA","Exit codes")}
|
|
136
|
+
0 ok | 1 usage | 2 auth | 3 forbidden | 4 validation | 5 not found | 6 rate limit | 7 network | 8 payment
|
|
127
137
|
|
|
128
138
|
Docs: https://dunsocial.com/docs/cli
|
|
129
|
-
`;function
|
|
139
|
+
`;function ie(e){return{auth:`dun auth
|
|
130
140
|
|
|
131
141
|
login [--token] Device-code browser login (default) or --token for CI
|
|
132
142
|
logout Clear local config
|
|
@@ -139,6 +149,21 @@ Docs: https://dunsocial.com/docs/cli
|
|
|
139
149
|
current Show default workspace
|
|
140
150
|
`,workspaces:"Alias of workspace",accounts:`dun accounts list [--platform <name>]
|
|
141
151
|
dun accounts reddit flairs <subreddit> # alias of dun reddit flairs
|
|
152
|
+
dun accounts pinterest boards # alias of dun pinterest boards
|
|
153
|
+
`,pinterest:`dun pinterest
|
|
154
|
+
|
|
155
|
+
boards List boards for the connected Pinterest account
|
|
156
|
+
create-board --name "..." Create a public board on Pinterest
|
|
157
|
+
create-board --name "..." --private
|
|
158
|
+
Create a secret board (not on the public profile)
|
|
159
|
+
|
|
160
|
+
Output fields for boards: id, name, privacy, url
|
|
161
|
+
create-board returns the live Pinterest board id. Use that boardId in
|
|
162
|
+
dun posts publish --meta '{"pinterest":{"boardId":"...","title":"...","description":"..."}}'
|
|
163
|
+
|
|
164
|
+
Example:
|
|
165
|
+
dun pinterest boards --json
|
|
166
|
+
dun pinterest create-board --name "Landing page" --json
|
|
142
167
|
`,reddit:`dun reddit
|
|
143
168
|
|
|
144
169
|
flairs <subreddit> List flair template ids for the connected Reddit account
|
|
@@ -152,10 +177,10 @@ Example:
|
|
|
152
177
|
|
|
153
178
|
list [--status] [--account] [--limit] [--offset]
|
|
154
179
|
get <id>
|
|
155
|
-
validate --accounts
|
|
180
|
+
validate --accounts --text [--media] [--meta] Dry-run schedule checks (no writes)
|
|
156
181
|
schedule --text --accounts (--at|--in) [--media] [--meta]
|
|
157
182
|
publish --text --accounts [--media] [--meta] [--natural] [--wait] [--wait-timeout 120]
|
|
158
|
-
reschedule <id> (--at|--in) [--text]
|
|
183
|
+
reschedule <id> (--at|--in) [--text] [--media] [--meta]
|
|
159
184
|
cancel <id>
|
|
160
185
|
delete <id> [--yes]
|
|
161
186
|
x-cap
|
|
@@ -170,16 +195,20 @@ Platform metadata (--meta / --metadata / --meta-json):
|
|
|
170
195
|
Optional reddit fields: body, url (link), flairText (only if textEditable), posterMediaAssetId, nsfw, spoiler
|
|
171
196
|
|
|
172
197
|
Reddit happy path:
|
|
173
|
-
1. dun media upload ./video.mp4 --json # rejects >
|
|
198
|
+
1. dun media upload ./video.mp4 --json # rejects >16GB locally
|
|
174
199
|
2. dun reddit flairs SUB --json # pick flairId
|
|
175
200
|
3. dun posts validate --accounts ID --media ASSET --meta '...' --json
|
|
176
201
|
4. dun posts publish --accounts ID --media ASSET --meta '...' --wait --json
|
|
177
202
|
|
|
178
|
-
|
|
179
|
-
1. dun accounts list --platform linkedin --json #
|
|
180
|
-
2. dun media upload ./clip.mp4 --json # mp4 or mov; <=
|
|
203
|
+
LinkedIn video (no --meta):
|
|
204
|
+
1. dun accounts list --platform linkedin --json # or linkedin-business for a Company Page
|
|
205
|
+
2. dun media upload ./clip.mp4 --json # mp4 or mov; <=16GB gallery, LinkedIn still ~512MB
|
|
181
206
|
3. dun posts publish --accounts ID --text "..." --media ASSET --wait --json
|
|
182
|
-
Rules: exactly one video OR images (not mixed). Company Pages
|
|
207
|
+
Rules: exactly one video OR images (not mixed). Personal profiles and Company Pages both accept video.
|
|
208
|
+
|
|
209
|
+
YouTube Shorts only:
|
|
210
|
+
Vertical or square video, 3 minutes or less. The uploaded asset must include width, height, and duration
|
|
211
|
+
or publish is rejected (landscape/unknown dimensions will not post as a regular YouTube video).
|
|
183
212
|
|
|
184
213
|
Notes:
|
|
185
214
|
- publish without --wait returns status scheduled (queued). Poll get or use --wait.
|
|
@@ -191,7 +220,7 @@ Notes:
|
|
|
191
220
|
|
|
192
221
|
Local preflight before upload-url:
|
|
193
222
|
images <= 8MB (jpeg/png/gif/webp)
|
|
194
|
-
video <=
|
|
223
|
+
video <= 16GB (mp4/mov/webm). X publish: default 20 min / 8 GB; Premium and Premium+ 125 min / 16 GB.
|
|
195
224
|
Oversized video error includes an ffmpeg compress one-liner.
|
|
196
225
|
`,memory:`dun memory collections list|create|get|update|delete
|
|
197
226
|
dun memory list|save|search|delete
|
|
@@ -216,45 +245,59 @@ dun skill path`,update:`dun update
|
|
|
216
245
|
(no args) Check npm and upgrade via detected package manager
|
|
217
246
|
--check Report current vs latest only
|
|
218
247
|
--force Reinstall latest even if versions match
|
|
248
|
+
--yes Needed for a silent-skipped major bump
|
|
249
|
+
|
|
250
|
+
On a human TTY with a global npm/bun/pnpm/yarn install, dun auto-upgrades
|
|
251
|
+
after a successful command. Agents, CI, --json, and npx never auto-update.
|
|
252
|
+
Disable with --no-update or DUN_UPDATE=0.
|
|
219
253
|
|
|
220
254
|
Aliases: dun upgrade, dun self-update
|
|
221
|
-
`,upgrade:"Alias of update","self-update":"Alias of update"}[e]??
|
|
222
|
-
`),
|
|
223
|
-
`).map((n)=>n.length))}function
|
|
224
|
-
CLI v${
|
|
225
|
-
`)}import{spawn as
|
|
226
|
-
Next: dun workspace list && dun workspace use <id|slug>`}async function
|
|
227
|
-
${
|
|
255
|
+
`,upgrade:"Alias of update","self-update":"Alias of update"}[e]??re}var Nt=["DunSocial","---------"].join(`
|
|
256
|
+
`),Se="DunSocial";var jn=Te(Nt),Wn=Te(Se);function Te(e){return Math.max(...e.split(`
|
|
257
|
+
`).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${U}`}function Ce(){return`${Se}
|
|
258
|
+
CLI v${U}`}var vt="https://dunsocial.com/docs/cli";function xt(e){if(!e.token)return"signed_out";if(!e.workspaceId)return"needs_workspace";return"ready"}function Ee(e){let n=xt(e),t=[`DunSocial CLI v${U}`,""];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 ${vt}`,""),t.join(`
|
|
259
|
+
`)}import{spawn as Lt}from"node:child_process";class Ne{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/${U}`,...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,he(r.status),Rt(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 Ne(e)}function Rt(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 ve(e,n,t,i){switch(n){case"login":return $t(e,t);case"logout":return Dt(e);case"whoami":return _t(e);case"status":return Mt(e);default:throw new d(`Unknown auth command "${n??""}". Try: login, logout, whoami, status`,c.USAGE)}}async function $t(e,n){let t=f(n,"token")||process.env.DUN_TOKEN;if(t)return Ot(e,t);return Pt(e,n)}function q(e){if(e)return"Logged in.";return`Logged in.
|
|
260
|
+
Next: dun workspace list && dun workspace use <id|slug>`}async function Ot(e,n){let t={...e,token:n},a=await b(t).get("/api/user/profile");if(await D({...e.config,apiUrl:e.apiUrl,token:n,defaultWorkspaceId:e.config.defaultWorkspaceId,defaultWorkspaceSlug:e.config.defaultWorkspaceSlug,defaultWorkspaceName:e.config.defaultWorkspaceName}),!e.json&&x(process.stdout)){m(q(Boolean(e.config.defaultWorkspaceId)),e);return}m({user:{id:a.id,name:a.name,email:a.email},configPath:P(),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(`
|
|
261
|
+
${Ce()}
|
|
228
262
|
|
|
229
263
|
`);else process.stderr.write(`
|
|
230
264
|
DunSocial CLI login
|
|
231
265
|
`),process.stderr.write(`───────────────────
|
|
232
266
|
`);process.stderr.write(`In your browser, open:
|
|
233
|
-
${
|
|
267
|
+
${i.verificationUrlComplete}
|
|
234
268
|
|
|
235
|
-
`),process.stderr.write(`Or go to ${
|
|
236
|
-
`),process.stderr.write(` ${
|
|
269
|
+
`),process.stderr.write(`Or go to ${i.verificationUrl} and enter code:
|
|
270
|
+
`),process.stderr.write(` ${i.userCode}
|
|
237
271
|
|
|
238
272
|
`),process.stderr.write(`Waiting for approval...
|
|
239
|
-
`)}else process.stderr.write(JSON.stringify({ok:!0,phase:"waiting",userCode:
|
|
240
|
-
`);if(c&&process.stdout.isTTY)try{await dt(s.verificationUrlComplete)}catch{}let r=Math.max(3,s.interval||5)*1000,o=Date.now()+(s.expiresIn||900)*1000;while(Date.now()<o){await ct(r);let i=await t.post("/api/cli/auth/poll",{deviceCode:s.deviceCode},{auth:!1});if(i.status==="pending")continue;if(i.status==="expired")throw new d("Login expired. Run dun auth login again.",a.AUTH);if(i.status==="denied")throw new d("Login was denied in the browser.",a.AUTH);if(i.status==="approved"){let l=i.token,u={...e,token:l},p=y(u),w=await p.get("/api/user/profile"),g={...e.config,apiUrl:e.apiUrl,token:l,defaultWorkspaceId:i.workspaceId||e.config.defaultWorkspaceId,defaultWorkspaceSlug:e.config.defaultWorkspaceSlug,defaultWorkspaceName:e.config.defaultWorkspaceName};if(i.workspaceId)try{let v=(await p.get("/api/workspaces")).find((Pe)=>Pe.id===i.workspaceId);if(v)g.defaultWorkspaceId=v.id,g.defaultWorkspaceSlug=v.slug,g.defaultWorkspaceName=v.name}catch{}await D(g);let k=Boolean(g.defaultWorkspaceId);if(!e.json&&U(process.stdout)){m(P(k),e);return}m({user:{id:w.id,name:w.name,email:w.email},workspaceId:g.defaultWorkspaceId??null,configPath:L(),method:"device",message:P(k)},e);return}}throw new d("Login timed out. Run dun auth login again.",a.AUTH)}async function ot(e){if(await re(),!e.json&&U(process.stdout)){m("Logged out. Config cleared.",e);return}m({message:"Logged out. Config cleared.",configPath:L()},e)}async function st(e){I(e);let t=await y(e).get("/api/user/profile");m({id:t.id,name:t.name,email:t.email,image:t.image??null,apiUrl:e.apiUrl,workspaceId:e.workspaceId??null},e)}async function at(e){let n=Boolean(e.token),t=null,s=null;if(n)try{t=await y(e).get("/api/user/profile")}catch(c){s=c instanceof Error?c.message:String(c)}m({authenticated:Boolean(t),tokenPresent:n,apiUrl:e.apiUrl,workspaceId:e.workspaceId??null,workspaceSlug:e.config.defaultWorkspaceSlug??null,workspaceName:e.config.defaultWorkspaceName??null,configPath:L(),user:t?{id:t.id,name:t.name,email:t.email}:null,error:s},e)}function ct(e){return new Promise((n)=>setTimeout(n,e))}async function dt(e){let n=process.platform,t=n==="darwin"?["open",e]:n==="win32"?["cmd","/c","start","",e]:["xdg-open",e];await new Promise((s,c)=>{let r=tt(t[0],t.slice(1),{stdio:"ignore",windowsHide:!0});r.on("error",c),r.on("close",()=>s())})}async function ge(e,n,t,s){I(e);let c=y(e);switch(n){case"list":case"ls":{let r=await c.get("/api/workspaces");m(r.map((o)=>({id:o.id,name:o.name,slug:o.slug,role:o.role,timezone:o.timezone,plan:o.subscription?.planName??null,status:o.subscription?.status??null})),{...e,emptyMessage:"No workspaces yet."});return}case"use":{let r=s[0];if(!r)throw new d("Usage: dun workspace use <id|slug>",a.USAGE);let i=(await c.get("/api/workspaces")).find((l)=>l.id===r||l.slug===r);if(!i)throw new d(`Workspace not found: ${r}. Run dun workspace list`,a.NOT_FOUND);await D({...e.config,apiUrl:e.apiUrl,token:e.token,defaultWorkspaceId:i.id,defaultWorkspaceSlug:i.slug,defaultWorkspaceName:i.name}),m({id:i.id,name:i.name,slug:i.slug,role:i.role,message:`Default workspace set to ${i.name}`},e);return}case"current":{if(!e.workspaceId&&!e.config.defaultWorkspaceId)throw new d("No workspace selected. Run dun workspace use <id|slug>",a.VALIDATION);let r=e.workspaceId||e.config.defaultWorkspaceId;try{let i=(await c.get("/api/workspaces")).find((l)=>l.id===r||l.slug===r);if(i){m({id:i.id,name:i.name,slug:i.slug,role:i.role,timezone:i.timezone},e);return}}catch{}m({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`,a.USAGE)}}async function we(e,n,t,s){I(e),A(e);let c=y(e);switch(n){case void 0:case"list":case"ls":{let r=await c.get("/api/social-accounts",{workspace:!0}),o=f(t,"platform","p")?.toLowerCase(),i=o?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"]}[o]??[o]).some((g)=>u.includes(g))}):r;m(i.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:o?`No ${o} accounts connected. Connect one in the DunSocial dashboard.`:"No accounts connected. Connect one in the DunSocial dashboard."});return}default:throw new d(`Unknown accounts command "${n}". Try: list (or: dun accounts reddit flairs <subreddit>)`,a.USAGE)}}import{readFileSync as pt}from"node:fs";var lt=/^(\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 J(e,n=new Date){let t=e.trim();if(!t)throw new d("Empty time value",a.VALIDATION);let s=t.match(lt);if(s){let o=Number(s[1]),i=s[2].toLowerCase(),l=ut(i)*o;return new Date(n.getTime()+l)}let c=t.match(/^in\s+(.+)$/i);if(c)return J(c[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).`,a.VALIDATION);return r}function ut(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}`,a.VALIDATION)}}function M(e){if(e.at&&e.in)throw new d("Pass only one of --at or --in",a.VALIDATION);if(e.at)return J(e.at).toISOString();if(e.in)return J(e.in).toISOString();throw new d("Schedule time required. Pass --at <iso> or --in <duration> (e.g. 2h).",a.VALIDATION)}function ke(e){if(!e)return[];return e.split(",").map((n)=>n.trim()).filter(Boolean)}function he(e,n){let t=f(e,"text","content","c","t");if(t)return t;let s=n?.reddit;if(s&&typeof s==="object"&&!Array.isArray(s)&&typeof s.title==="string"&&s.title.trim())return s.title.trim();throw new d("Missing --text",a.VALIDATION)}function q(e){let n=C(e,"accounts","account","a")??ke(f(e,"accounts","account","a"));if(!n||n.length===0)throw new d("Missing --accounts <id,id>",a.VALIDATION);return n}function j(e){return C(e,"media","media-urls")??ke(f(e,"media","media-urls"))}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}`,a.VALIDATION)}}var mt=new Set(["published","failed","cancelled"]);async function ft(e){await new Promise((n)=>setTimeout(n,e))}async function gt(e,n,t,s){let c=Date.now()+s*1000,r=new Map,o=1500;while(r.size<t.length){if(Date.now()>c){let u=t.filter((p)=>!r.has(p.id)).map((p)=>p.id);throw new d(`Timed out waiting for publish after ${s}s. Still pending: ${u.join(", ")}. Poll with: dun posts get <id> --json`,a.NETWORK,{pending:u})}for(let u of t){if(r.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}`),mt.has(p.status))r.set(u.id,p)}if(r.size>=t.length)break;await ft(o),o=Math.min(o*1.4,8000)}let i=t.map((u)=>r.get(u.id)),l=i.filter((u)=>u.status==="failed");if(l.length>0){let u=l.map((p)=>`${p.id}: ${p.error||"publish failed"}`).join("; ");throw new d(`Publish failed — ${u}`,a.VALIDATION,{posts:i})}return i}async function wt(e,n){let t=[],s=n.meta?.reddit??null;if(!s||typeof s!=="object")throw new d("Reddit accounts require --meta with a reddit object, e.g. "+`--meta '{"reddit":{"subreddit":"…","kind":"self","title":"…","flairId":"…"}}'`,a.VALIDATION);let c=(s.subreddit??"").trim().replace(/^\/?r\//i,""),r=(s.title??"").trim(),o=(s.kind??"").trim(),i=(s.flairId??"").trim(),l=(s.flairText??"").trim();if(!c)throw new d("metadata.reddit.subreddit is required",a.VALIDATION);if(!r)throw new d("metadata.reddit.title is required",a.VALIDATION);if(r.length>300)throw new d(`Reddit title exceeds 300 characters: ${r.length}/300`,a.VALIDATION);if(!["self","link","image","video"].includes(o))throw new d("metadata.reddit.kind must be one of: self, link, image, video",a.VALIDATION);if(t.push(`kind=${o}`),t.push(`titleLength=${r.length}`),l&&!i)throw new d(`Can't set flairText without flairId for r/${c}. Run: dun reddit flairs ${c} --json`,a.VALIDATION);if(o==="link"){if(!s.url?.trim())throw new d("Reddit link posts require metadata.reddit.url",a.VALIDATION);try{new URL(s.url)}catch{throw new d("metadata.reddit.url is invalid",a.VALIDATION)}if(n.media.length>0)throw new d("Reddit link posts do not accept --media",a.VALIDATION)}else if(o==="image"||o==="video"){if(n.media.length!==1)throw new d(`Reddit ${o} posts require exactly one --media asset id`,a.VALIDATION)}else if(n.media.length>0)throw new d("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&&!i)throw new d(`r/${c} requires post flair. Discover ids with: dun reddit flairs ${c} --json`,a.VALIDATION);let p=u?.title_text_max_length??300;if(r.length>p)throw new d(`Reddit title too long for r/${c}: ${r.length}/${p}`,a.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`,a.VALIDATION);if(i){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 g=(w.choices??[]).find((k)=>k.id===i);if(!g)throw new d(`Unknown flairId "${i}" for r/${c}. Run: dun reddit flairs ${c} --json`,a.VALIDATION);if(g.textEditable===!1&&l&&l!==g.text)throw new d(`Customization not allowed for this flair template. Use flairId "${g.id}" alone`+(g.text?` (template text: "${g.text}")`:""),a.VALIDATION);t.push(`flairId=${g.id}`)}return{ok:!0,checks:t,reddit:{subreddit:c,kind:o,title:r,flairId:i||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 ye(e,n,t,s){I(e),A(e);let c=y(e);switch(n){case"list":case"ls":{let r=await c.get("/api/posts",{workspace:!0,query:{status:f(t,"status","s"),socialAccountId:f(t,"account","social-account"),groupId:f(t,"group"),limit:E(t,"limit")??50,offset:E(t,"offset")??0}});m(r,{...e,emptyMessage:"No posts yet. Schedule one with dun posts schedule."});return}case"get":{let r=s[0];if(!r)throw new d("Usage: dun posts get <id>",a.USAGE);let o=await c.get(`/api/posts/${r}`,{workspace:!0});m(o,e);return}case"validate":{let r=q(t),o=j(t),i=O(t)??{},l=f(t,"text","content","c","t"),u=Boolean(i.reddit),p=u;if(!u){let g=await c.get("/api/social-accounts",{workspace:!0});p=r.some((k)=>(g.find((v)=>v.id===k)?.providerName||"").toLowerCase().includes("reddit"))}if(!p){m({ok:!0,checks:["no reddit-specific rules for selected accounts"],accounts:r,media:o},e);return}let w=await wt(c,{accounts:r,text:l,media:o,meta:i});m(w,e);return}case"schedule":{let r=O(t)??{},o=he(t,r),i=q(t),l=M({at:f(t,"at"),in:f(t,"in")}),u=await c.post("/api/posts/schedule",{content:o,socialAccountIds:i,scheduledAt:l,mediaUrls:j(t),metadata:r},{workspace:!0});m(u,e);return}case"publish":case"publish-now":{let r=O(t)??{},o=he(t,r),i=q(t),l=await c.post("/api/posts/publish-now",{content:o,socialAccountIds:i,mediaUrls:j(t),metadata:r,naturalPosting:h(t,"natural","natural-posting")},{workspace:!0});if(!h(t,"wait")){m(l,e);return}let p=Array.isArray(l)?l:[];if(p.length===0||!p.every((k)=>typeof k?.id==="string"))throw new d("publish --wait expected an array of created posts with ids",a.NETWORK,{data:l});let w=E(t,"wait-timeout","timeout")??120,g=await gt(c,e,p,w);m({queued:p,posts:g,status:g.every((k)=>k.status==="published")?"published":g[0]?.status},e);return}case"reschedule":{let r=s[0];if(!r)throw new d("Usage: dun posts reschedule <id> --at|--in",a.USAGE);let i={scheduledAt:M({at:f(t,"at"),in:f(t,"in")})},l=f(t,"text","content","c","t");if(l)i.content=l;let u=j(t);if(u.length)i.mediaUrls=u;let p=O(t);if(p)i.metadata=p;if(!i.content){let g=await c.get(`/api/posts/${r}`,{workspace:!0});if(i.content=g.content,!i.metadata&&g.metadata)i.metadata=g.metadata;if(!i.mediaUrls&&g.mediaUrls)i.mediaUrls=g.mediaUrls}let w=await c.post(`/api/posts/${r}/reschedule`,i,{workspace:!0});m(w,e);return}case"cancel":{let r=s[0];if(!r)throw new d("Usage: dun posts cancel <id>",a.USAGE);let o=await c.post(`/api/posts/${r}/cancel`,void 0,{workspace:!0});m(o??{id:r,status:"cancelled"},e);return}case"delete":case"rm":{let r=s[0];if(!r)throw new d("Usage: dun posts delete <id> [--yes]",a.USAGE);if(!e.yes&&!h(t,"yes","y"))throw new d("Refusing to delete without --yes (or CI/yes mode)",a.VALIDATION);let o=await c.delete(`/api/posts/${r}`,void 0,{workspace:!0});m(o??{id:r,deleted:!0},e);return}case"x-cap":case"xcap":{let r=await c.get("/api/posts/x-cap-usage",{workspace:!0});m(r,e);return}case"schedule-thread":case"publish-thread":case"publish-thread-now":{let r=f(t,"file","f"),o=f(t,"account","accounts","a")??q(t)[0];if(!o)throw new d("Missing --account <xAccountId>",a.VALIDATION);let i;if(r){let u;try{u=pt(r,"utf8")}catch(w){throw new d(`Could not read file: ${w.message}`,a.VALIDATION)}let p=JSON.parse(u);if(Array.isArray(p))i=p;else if(p&&typeof p==="object"&&Array.isArray(p.tweets))i=p.tweets;else throw new d("Thread file must be an array of tweets or { tweets: [] }",a.VALIDATION)}else{let u=f(t,"text","content");if(!u)throw new d('Provide --file thread.json (array of {content}) or --text "tweet1||tweet2"',a.VALIDATION);i=u.split("||").map((p)=>({content:p.trim()})).filter((p)=>p.content)}if(i.length<2)throw new d("Threads require at least 2 tweets",a.VALIDATION);if(n==="schedule-thread"){let u=M({at:f(t,"at"),in:f(t,"in")}),p=await c.post("/api/posts/schedule-thread",{socialAccountId:o,tweets:i,scheduledAt:u,metadata:O(t)??{}},{workspace:!0});m(p,e);return}let l=await c.post("/api/posts/publish-thread-now",{socialAccountId:o,tweets:i,metadata:O(t)??{}},{workspace:!0});m(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`,a.USAGE)}}async function Ie(e,n,t,s){I(e),A(e);let c=y(e);switch(n){case"list":case"ls":{let r=await c.get("/api/drafts",{workspace:!0,query:{limit:E(t,"limit")??50,offset:E(t,"offset")??0}});m(r,{...e,emptyMessage:'No drafts yet. Create one with dun drafts create --text "..."'});return}case"get":{let r=s[0];if(!r)throw new d("Usage: dun drafts get <id>",a.USAGE);let o=await c.get(`/api/drafts/${r}`,{workspace:!0});m(o,e);return}case"create":{let r=f(t,"text","content","c","t");if(!r)throw new d("Missing --text",a.VALIDATION);let o=await c.post("/api/drafts",{content:r,name:f(t,"name","n"),socialAccountIds:C(t,"accounts","account","a")??[],selectedPlatforms:C(t,"platforms","platform","p")??[],mediaUrls:C(t,"media")??[]},{workspace:!0});m(o,e);return}case"update":{let r=s[0];if(!r)throw new d("Usage: dun drafts update <id> --text ...",a.USAGE);let o={id:r},i=f(t,"text","content","c","t");if(i)o.content=i;let l=f(t,"name","n");if(l!==void 0)o.name=l;let u=C(t,"accounts","account","a");if(u)o.socialAccountIds=u;let p=C(t,"platforms","platform","p");if(p)o.selectedPlatforms=p;let w=C(t,"media");if(w)o.mediaUrls=w;let g=await c.patch(`/api/drafts/${r}`,o,{workspace:!0});m(g,e);return}case"delete":case"rm":{let r=s[0];if(!r)throw new d("Usage: dun drafts delete <id> [--yes]",a.USAGE);if(!e.yes&&!h(t,"yes","y"))throw new d("Refusing to delete without --yes",a.VALIDATION);let o=await c.delete(`/api/drafts/${r}`,void 0,{workspace:!0});m(o??{id:r,deleted:!0},e);return}default:throw new d(`Unknown drafts command "${n??""}". Try: list, get, create, update, delete`,a.USAGE)}}import{readFileSync as ht,existsSync as kt,statSync as yt}from"node:fs";import{basename as It}from"node:path";var be=["image/jpeg","image/png","image/gif","image/webp"],Ae=["video/mp4","video/quicktime","video/webm"];function Se(e){if(e<1024)return`${e}B`;if(e<1048576)return`${(e/1024).toFixed(1)}KB`;return`${(e/1048576).toFixed(1)}MB`}function Te(e){let{mimeType:n,fileSize:t}=e,s=be.includes(n),c=Ae.includes(n);if(!s&&!c)return{ok:!1,message:`Unsupported MIME type "${n}". Allowed images: ${be.join(", ")}; videos: ${Ae.join(", ")}`};if(c&&t>268435456)return{ok:!1,message:`Video files must be less than 256MB (got ${Se(t)}). Compress first, e.g.:
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
`),
|
|
244
|
-
|
|
245
|
-
${s}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
273
|
+
`)}else process.stderr.write(JSON.stringify({ok:!0,phase:"waiting",userCode:i.userCode,verificationUrlComplete:i.verificationUrlComplete})+`
|
|
274
|
+
`);if(a&&process.stdout.isTTY)try{await Wt(i.verificationUrlComplete)}catch{}let r=Math.max(3,i.interval||5)*1000,s=Date.now()+(i.expiresIn||900)*1000;while(Date.now()<s){await jt(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},p=b(u),g=await p.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 p.get("/api/workspaces")).find((pt)=>pt.id===o.workspaceId);if(N)w.defaultWorkspaceId=N.id,w.defaultWorkspaceSlug=N.slug,w.defaultWorkspaceName=N.name}catch{}await D(w);let k=Boolean(w.defaultWorkspaceId);if(!e.json&&x(process.stdout)){m(q(k),e);return}m({user:{id:g.id,name:g.name,email:g.email},workspaceId:w.defaultWorkspaceId??null,configPath:P(),method:"device",message:q(k)},e);return}}throw new d("Login timed out. Run dun auth login again.",c.AUTH)}async function Dt(e){if(await be(),!e.json&&x(process.stdout)){m("Logged out. Config cleared.",e);return}m({message:"Logged out. Config cleared.",configPath:P()},e)}async function _t(e){y(e);let t=await b(e).get("/api/user/profile");m({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)}m({authenticated:Boolean(t),tokenPresent:n,apiUrl:e.apiUrl,workspaceId:e.workspaceId??null,workspaceSlug:e.config.defaultWorkspaceSlug??null,workspaceName:e.config.defaultWorkspaceName??null,configPath:P(),user:t?{id:t.id,name:t.name,email:t.email}:null,error:i},e)}function jt(e){return new Promise((n)=>setTimeout(n,e))}async function Wt(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=Lt(t[0],t.slice(1),{stdio:"ignore",windowsHide:!0});r.on("error",a),r.on("close",()=>i())})}function xe(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 qt(e){return e?.hasAccess===!1}function Vt(e){if(!qt(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}
|
|
275
|
+
Subscribe: ${t}`:n}function Bt(){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)=>Vt(i)).filter((i)=>Boolean(i));if(t.length===0)return;if(process.stdout.write(`${t.join(`
|
|
276
|
+
`)}
|
|
277
|
+
`),t.length===e.length&&e.length>0)process.stdout.write(`${Bt()}
|
|
278
|
+
`)}async function Re(e,n,t,i){y(e);let a=b(e);switch(n){case"list":case"ls":{let r=await a.get("/api/workspaces");m(r.map(xe),{...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 D({...e.config,apiUrl:e.apiUrl,token:e.token,defaultWorkspaceId:o.id,defaultWorkspaceSlug:o.slug,defaultWorkspaceName:o.name}),m({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){m({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{}m({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 Le(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"]}[s]??[s]).some((w)=>u.includes(w))}):r;m(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 Kt}from"node:fs";var Gt=/^(\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(Gt);if(i){let s=Number(i[1]),o=i[2].toLowerCase(),l=Ft(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 Ft(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 Oe(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")??Oe(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")??Oe(f(e,"media","media-urls"))}function Ht(e,n){if(e?.reddit)return!0;return n.some((t)=>t.toLowerCase().includes("reddit"))}function zt(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 Jt=new Set(["published","failed","cancelled"]);async function Yt(e){await new Promise((n)=>setTimeout(n,e))}async function Xt(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((p)=>!r.has(p.id)).map((p)=>p.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 p=await e.get(`/api/posts/${u.id}`,{workspace:!0});if(R(n.debug,`wait status ${u.id}=${p.status}`),Jt.has(p.status))r.set(u.id,p)}if(r.size>=t.length)break;await Yt(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((p)=>`${p.id}: ${p.error||"publish failed"}`).join("; ");throw new d(`Publish failed — ${u}`,c.VALIDATION,{posts:o})}return o}async function $e(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(!Ht(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 p=u?.title_text_max_length??300;if(r.length>p)throw new d(`Reddit title too long for r/${a}: ${r.length}/${p}`,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:p}}}}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:C(t,"limit")??50,offset:C(t,"offset")??0}});m(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});m(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 p=Boolean(o.reddit),g=p;if(!p){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});m({ok:!0,...u,reddit:w},e);return}m({ok:!0,...u},e);return}case"schedule":{let r=O(t)??{},s=se(t,r),o=B(t),l=G(t);await $e(a,o,{text:s,media:l,meta:r});let u=V({at:f(t,"at"),in:f(t,"in")}),p=await a.post("/api/posts/schedule",{content:s,socialAccountIds:o,scheduledAt:u,mediaUrls:l,metadata:r},{workspace:!0});m(p,e);return}case"publish":case"publish-now":{let r=O(t)??{},s=se(t,r),o=B(t),l=G(t);await $e(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")){m(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=C(t,"wait-timeout","timeout")??120,k=await Xt(a,e,g,w);m({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=zt({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});m(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});m(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});m(s??{id:r,deleted:!0},e);return}case"x-cap":case"xcap":{let r=await a.get("/api/posts/x-cap-usage",{workspace:!0});m(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=Kt(r,"utf8")}catch(g){throw new d(`Could not read file: ${g.message}`,c.VALIDATION)}let p=JSON.parse(u);if(Array.isArray(p))o=p;else if(p&&typeof p==="object"&&Array.isArray(p.tweets))o=p.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((p)=>({content:p.trim()})).filter((p)=>p.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")}),p=await a.post("/api/posts/schedule-thread",{socialAccountId:s,tweets:o,scheduledAt:u,metadata:O(t)??{}},{workspace:!0});m(p,e);return}let l=await a.post("/api/posts/publish-thread-now",{socialAccountId:s,tweets:o,metadata:O(t)??{}},{workspace:!0});m(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 De(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:C(t,"limit")??50,offset:C(t,"offset")??0}});m(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});m(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});m(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 p=E(t,"platforms","platform","p");if(p)s.selectedPlatforms=p;let g=E(t,"media");if(g)s.mediaUrls=g;let w=await a.patch(`/api/drafts/${r}`,s,{workspace:!0});m(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});m(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 Zt,existsSync as Qt,statSync as en}from"node:fs";import{basename as tn}from"node:path";var _e=["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 je(e){let{mimeType:n,fileSize:t}=e,i=_e.includes(n),a=Me.includes(n);if(!i&&!a)return{ok:!1,message:`Unsupported MIME type "${n}". Allowed images: ${_e.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.:
|
|
279
|
+
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 nn(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 We(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:C(t,"limit")??50,offset:C(t,"offset")??0}});m(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});m(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(!Qt(r))throw new d(`File not found: ${r}`,c.VALIDATION);let s=tn(r),o=en(r).size,l=f(t,"mime","type","content-type")||nn(s),u=f(t,"alt","alt-text"),p=je({mimeType:l,fileSize:o});if(!p.ok)throw new d(p.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=Zt(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});m(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});m(s??{id:r,deleted:!0},e);return}default:throw new d(`Unknown media command "${n??""}". Try: list, get, upload, delete`,c.USAGE)}}async function qe(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});m(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});m(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});m(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"),p=h(t,"private"),g=h(t,"public");if(p&&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(p)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});m(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});m(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:C(t,"limit")??100}});m(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});m(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:C(t,"top-k","topk","k")??5},{workspace:!0});m(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});m(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 rn}from"node:fs";async function Ve(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});m(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=rn(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});m(w,e);return}default:throw new d(`Unknown personalization command "${n}". Try: get, set`,c.USAGE)}}function Be(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>
|
|
280
|
+
Example: dun reddit flairs tamilyapping --json`,c.USAGE);let s=Be(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}));m({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=Be(r),o=await a.get("/api/social-accounts/reddit/post-requirements",{workspace:!0,query:{subreddit:s}});m({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});m({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]
|
|
281
|
+
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});m(s,e);return}default:throw new d(`Unknown pinterest command "${n??""}". Try: boards, create-board`,c.USAGE)}}import{existsSync as Ke}from"node:fs";import{mkdir as He,writeFile as ze,readFile as me}from"node:fs/promises";import{dirname as fe,join as A}from"node:path";import{homedir as F}from"node:os";import{fileURLToPath as on}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 an(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")}],ue=new Map;for(let e of _){ue.set(e.id,e);for(let n of e.aliases??[])ue.set(n,e)}var Ge=`Usage: dun skill install <${_.map((e)=>e.id).join("|")}|all>`;function pe(){let e=fe(on(import.meta.url)),n=A("skills","dunsocial","SKILL.md"),t=[A(e,"..",n),A(e,"..","..",n)];return t.find(Ke)??t[0]}function sn(){let e=F();return _.map((n)=>({id:n.id,label:n.label,path:n.dest(e),aliases:n.aliases??[]}))}async function Je(){try{return await me(pe(),"utf8")}catch{throw new d(`Bundled skill not found at ${pe()}. Reinstall dunsocial.`,c.USAGE)}}async function Ye(e,n){await He(fe(e),{recursive:!0}),await ze(e,n,"utf8")}async function an(e){let n=A(F(),".codex","AGENTS.md");await He(fe(n),{recursive:!0});let t="";try{t=await me(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(`
|
|
282
|
+
`),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()}
|
|
283
|
+
|
|
284
|
+
${i}
|
|
285
|
+
`;else s=`${i}
|
|
286
|
+
`;return await ze(n,s,"utf8"),n}async function K(e){let n=e?.home??F(),t=e?.markdown;if(t===void 0)try{t=await Je()}catch{return[]}let i=[];for(let a of _){let r=a.dest(n);if(!Ke(r))continue;let s="";try{s=await me(r,"utf8")}catch{continue}if(s===t){i.push({target:a.id,path:r,updated:!1});continue}if(await Ye(r,t),a.afterInstall)try{await a.afterInstall(r)}catch{}i.push({target:a.id,path:r,updated:!0})}return i}async function Fe(e,n){let t=e.dest(F());await Ye(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 Xe(e,n,t,i){switch(n){case"path":{m({path:pe()},e);return}case"list":{m({targets:sn(),hint:"Install with: dun skill install <target|all>"},e);return}case"install":{let a=(i[0]||"").toLowerCase();if(!a)throw new d(Ge,c.USAGE);let r=await Je();if(a==="all"){let l=[];for(let u of _){let p=await Fe(u,r);l.push({target:p.target,path:p.path})}m({target:"all",count:l.length,installed:l,message:`Installed DunSocial skill for ${l.length} agents.`},e);return}let s=ue.get(a);if(!s)throw new d(`Unknown agent "${a}". ${Ge}
|
|
287
|
+
Try: dun skill list`,c.USAGE);let o=await Fe(s,r);m(o,e);return}default:throw new d(`Unknown skill command "${n??""}". Try: install, list, path`,c.USAGE)}}import{spawn as cn}from"node:child_process";import{realpathSync as dn}from"node:fs";var S="dunsocial",ln=`https://registry.npmjs.org/${S}/latest`,un=400;function M(e=process.argv[1]??""){let n=e.replaceAll("\\","/");try{n=dn(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 j(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 Ze(e){return Number.parseInt(e.replace(/^v/,"").split(".")[0]??"0",10)||0}function Qe(e,n){return Ze(n)>Ze(e)}async function H(e=fetch,n){let t=await e(ln,{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 ge(e=un,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=cn(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 et(e,n,t){let i=h(t,"check"),a=h(t,"force"),r=U,s=M(),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=j(o,r)>0,u={current:r,latest:o,upToDate:!l,packageManager:s.packageManager,command:s.manual};if(!l&&!a){m(e.json?u:`Already on the latest version (${r}).`,e);return}if(i){m(e.json?{...u,updateAvailable:l}:`Update available: ${r} → ${o}
|
|
249
288
|
Run: dun update
|
|
250
|
-
Or: ${
|
|
251
|
-
Cannot auto-update this install (${
|
|
252
|
-
Run: ${
|
|
253
|
-
`);let p=await
|
|
254
|
-
|
|
255
|
-
`);
|
|
256
|
-
`)}
|
|
257
|
-
`)}async function
|
|
258
|
-
|
|
259
|
-
`);
|
|
260
|
-
`)
|
|
289
|
+
Or: ${s.manual}`,e);return}if(!s.command||!s.args){m(e.json?{...u,applied:!1,hint:s.manual}:`Update available: ${r} → ${o}
|
|
290
|
+
Cannot auto-update this install (${s.packageManager}).
|
|
291
|
+
Run: ${s.manual}`,e);return}if(!e.json&&!e.quiet)process.stderr.write(`Updating ${S}: ${r} → ${o} via ${s.packageManager}...
|
|
292
|
+
`);let p=await z(s.command,s.args);if(p!==0)throw new d(`Update command failed (exit ${p}). Try: ${s.manual}`,c.NETWORK);try{await K()}catch{}m(e.json?{...u,applied:!0}:`Updated via ${s.packageManager}. Run dun --version to confirm.`,e)}import{mkdir as pn,readFile as mn,writeFile as fn}from"node:fs/promises";import{dirname as gn,join as tt}from"node:path";import{homedir as wn}from"node:os";var nt=86400000,hn=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:tt(wn(),".cache");return tt(t,"dunsocial","update.json")}function kn(e){let n=(e??"").trim().toLowerCase();return n==="0"||n==="never"||n==="off"||n==="false"||n==="no"}function bn(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 yn(e){return Boolean(e&&hn.has(e))}function In(e){return Boolean(e.command&&e.args)}function rt(e){let n=e.env??process.env;if(h(e.flags,"no-update"))return!1;if(h(e.flags,"json","j"))return!1;if(yn(e.group))return!1;if(!e.stdoutIsTty)return!1;if(n.DUN_JSON==="1")return!1;if(bn(n))return!1;if(kn(n.DUN_UPDATE))return!1;return!0}function it(e,n=Date.now(),t=nt){if(!e.lastCheckedAt||!e.latest)return!1;let i=Date.parse(e.lastCheckedAt);if(!Number.isFinite(i))return!1;return n-i<t}function An(e,n,t=Date.now(),i=nt){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 ot(e=Y()){try{let n=await mn(e,"utf8");return JSON.parse(n)}catch{return{}}}async function J(e,n=Y()){return await pn(gn(n),{recursive:!0}),await fn(n,`${JSON.stringify(e,null,2)}
|
|
293
|
+
`,"utf8"),e}async function st(e){let n=e?.cachePath??Y(),t=e?.now??Date.now(),i=await ot(n);if(it(i,t))return i;let a=await(e?.fetchLatest??ge)();if(!a)return i;return J({...i,lastCheckedAt:new Date(t).toISOString(),latest:a},n)}async function Sn(e){let n=e.write??((o)=>process.stderr.write(o)),t=e.cachePath??Y(),i=e.now??Date.now(),a=e.install??M();if(!In(a))return{applied:!1,reason:"no-install-command"};let r=await e.checkPromise??await ot(t),s=r.latest;if(!s||!it(r,i)){let o=await(e.fetchLatest??ge)();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(j(s,e.current)<=0)return{applied:!1,reason:"up-to-date",latest:s};if(Qe(e.current,s)){if(!e.quiet)n(`Major update available: ${e.current} -> ${s}. Run: dun update --yes
|
|
294
|
+
`);return{applied:!1,reason:"major",latest:s}}if(!An(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}
|
|
295
|
+
`);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}
|
|
296
|
+
`);return{applied:!1,reason:"apply-failed",latest:s}}}async function at(e){try{await e.refreshSkills();let t=await(e.apply??Sn)({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 ct(){if(x(process.stdout))process.stdout.write(`${Ue()}
|
|
297
|
+
|
|
298
|
+
`);process.stdout.write(`${re}
|
|
299
|
+
`)}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(`${Ee({token:n,workspaceId:t,config:e})}
|
|
300
|
+
`)}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(`${U}
|
|
301
|
+
`),c.OK;if(t==="help"||!t&&ee(r)){let l=t==="help"?i||s[0]:void 0;if(l)process.stdout.write(`${ie(l)}
|
|
302
|
+
`);else ct();return c.OK}if(!t){if(x(process.stdout))await Tn();else ct();return c.OK}if(ee(r))return process.stdout.write(`${ie(t)}
|
|
303
|
+
`),c.OK;let o=await ye({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 m({version:U},o),c.OK;try{switch(t){case"auth":await ve(o,i,r,s);break;case"workspace":case"workspaces":await Re(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}await Le(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"post":case"posts":await Pe(o,i,r,s);break;case"draft":case"drafts":await De(o,i,r,s);break;case"media":await We(o,i,r,s);break;case"memory":{let l=a?[a,i].filter(Boolean):i?[i]:[];await qe(o,l,r,s);break}case"personalization":case"voice":await Ve(o,i,r,s);break;case"skill":case"skills":await Xe(o,i,r,s);break;case"update":case"upgrade":case"self-update":await et(o,i,r);break;case"version":m({version:U},o);break;default:throw new d(`Unknown command "${t}". Run dun --help`,c.USAGE)}return c.OK}catch(l){return Ae(l,o)}}var dt=process.argv.slice(2),we=Q(dt),lt=rt({flags:we.flags,group:we.group,stdoutIsTty:Boolean(process.stdout.isTTY),env:process.env}),Cn=lt?st():Promise.resolve(void 0),ut=await Un(dt);if(ut===c.OK&<)await at({current:U,checkPromise:Cn,quiet:h(we.flags,"quiet","q"),refreshSkills:K});process.exit(ut);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dunsocial",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "DunSocial agent-first CLI — schedule and publish social posts from the terminal, CI, and coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"postinstall": "node ./scripts/postinstall.js"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@types/bun": "
|
|
45
|
+
"@types/bun": "1.3.14",
|
|
46
46
|
"@types/node": "^22",
|
|
47
47
|
"typescript": "^5.9.2"
|
|
48
48
|
}
|
|
@@ -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
|
|
@@ -49,6 +51,8 @@ PAT notes:
|
|
|
49
51
|
3. `dun accounts list --json` — capture social account **ids**
|
|
50
52
|
4. Act (schedule / publish / draft / media / memory / personalization)
|
|
51
53
|
|
|
54
|
+
If `workspace list --json` (or `use` / `current`) shows `hasAccess: false`, tell the user to open `billingUrl` in a browser. Do **not** retry `posts publish`, schedule, drafts, media, or memory until they subscribe — those commands return exit `8` / `payment_required`.
|
|
55
|
+
|
|
52
56
|
Do **not** invent account IDs. Do **not** treat `drafts create` as a successful publish.
|
|
53
57
|
|
|
54
58
|
## Schedule a post
|
|
@@ -83,15 +87,15 @@ dun media upload ./shot.png --alt "Product screenshot" --json
|
|
|
83
87
|
dun posts schedule --text "…" --accounts acc_x --media <assetId> --in 30m --json
|
|
84
88
|
```
|
|
85
89
|
|
|
86
|
-
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:
|
|
87
91
|
|
|
88
92
|
```bash
|
|
89
93
|
ffmpeg -i input.mp4 -vf scale=-2:1080 -c:v libx264 -crf 23 -c:a aac output.mp4
|
|
90
94
|
```
|
|
91
95
|
|
|
92
|
-
##
|
|
96
|
+
## LinkedIn video
|
|
93
97
|
|
|
94
|
-
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.
|
|
95
99
|
|
|
96
100
|
```bash
|
|
97
101
|
dun accounts list --platform linkedin --json
|
|
@@ -104,6 +108,16 @@ dun posts publish \
|
|
|
104
108
|
--json
|
|
105
109
|
```
|
|
106
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
|
+
|
|
107
121
|
## Reddit (special)
|
|
108
122
|
|
|
109
123
|
Reddit needs platform metadata under `--meta`. Discover flairs first — many subs require a **template id**, not free text.
|
|
@@ -141,6 +155,7 @@ dun posts validate \
|
|
|
141
155
|
--media <assetId> \
|
|
142
156
|
--meta '{"reddit":{"subreddit":"tamilyapping","kind":"video","title":"Approved title","flairId":"<id>"}}' \
|
|
143
157
|
--json
|
|
158
|
+
# calls POST /api/posts/validate, then live Reddit flair/requirements checks
|
|
144
159
|
|
|
145
160
|
dun posts publish \
|
|
146
161
|
--accounts <redditAccountId> \
|
|
@@ -204,6 +219,7 @@ Require `--yes` (or `CI=1`) for `delete`.
|
|
|
204
219
|
| 5 | not found |
|
|
205
220
|
| 6 | rate limited |
|
|
206
221
|
| 7 | network |
|
|
222
|
+
| 8 | payment required (unpaid workspace — open `billingUrl`) |
|
|
207
223
|
|
|
208
224
|
Failure JSON:
|
|
209
225
|
|