dunsocial 0.9.0 → 0.10.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 CHANGED
@@ -60,6 +60,81 @@ dun posts publish \
60
60
  --accounts ACCOUNT_ID
61
61
  ```
62
62
 
63
+ `publish` queues the post (`status: scheduled`). To wait until it finishes:
64
+
65
+ ```bash
66
+ dun posts publish \
67
+ --text "Hello from DunSocial" \
68
+ --accounts ACCOUNT_ID \
69
+ --wait
70
+ ```
71
+
72
+ ---
73
+
74
+ ## LinkedIn (personal video)
75
+
76
+ Personal LinkedIn supports **images** (up to 20) **or exactly one video** (MP4/MOV, ≤256MB). Do not mix video and images. Company Pages (`linkedin-business`) stay images-only — no `--meta` needed.
77
+
78
+ ```bash
79
+ # 1. Find your personal LinkedIn account id
80
+ dun accounts list --platform linkedin --json
81
+
82
+ # 2. Upload the clip
83
+ dun media upload ./clip.mp4 --json
84
+
85
+ # 3. Publish (or schedule with --in / --at)
86
+ dun posts publish \
87
+ --accounts LINKEDIN_ACCOUNT_ID \
88
+ --text "Shipping notes from this week" \
89
+ --media ASSET_ID \
90
+ --wait \
91
+ --json
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Reddit
97
+
98
+ Reddit needs platform options via `--meta`. Many subreddits also require a flair **template id**.
99
+
100
+ ```bash
101
+ # 1. Upload media first (videos must be under 256MB)
102
+ dun media upload ./demo.mp4 --json
103
+
104
+ # 2. List flairs for the subreddit (use id, not just the label text)
105
+ dun reddit flairs tamilyapping --json
106
+
107
+ # 3. Optional dry-run against subreddit rules
108
+ dun posts validate \
109
+ --accounts REDDIT_ACCOUNT_ID \
110
+ --media ASSET_ID \
111
+ --meta '{"reddit":{"subreddit":"tamilyapping","kind":"video","title":"Your title","flairId":"FLAIR_ID"}}' \
112
+ --json
113
+
114
+ # 4. Publish and wait for the real permalink
115
+ dun posts publish \
116
+ --accounts REDDIT_ACCOUNT_ID \
117
+ --media ASSET_ID \
118
+ --meta '{"reddit":{"subreddit":"tamilyapping","kind":"video","title":"Your title","flairId":"FLAIR_ID"}}' \
119
+ --wait \
120
+ --json
121
+ ```
122
+
123
+ `--meta.reddit` fields:
124
+
125
+ | Field | Required | Notes |
126
+ |-------|----------|--------|
127
+ | `subreddit` | yes | without `r/` |
128
+ | `kind` | yes | `self`, `link`, `image`, or `video` |
129
+ | `title` | yes | |
130
+ | `flairId` | when the sub requires flair | from `dun reddit flairs` |
131
+ | `url` | for `link` | |
132
+ | `flairText` | no | only if that flair's `textEditable` is true |
133
+
134
+ Prefer `flairId` alone. Sending `flairText` without `flairId` always fails.
135
+
136
+ Also useful: `dun reddit requirements <subreddit> --json`.
137
+
63
138
  ---
64
139
 
65
140
  ## Automated posting (CI)
@@ -89,6 +164,8 @@ dun --help
89
164
  dun auth status
90
165
  dun posts list
91
166
  dun media upload ./photo.png
167
+ dun reddit flairs some-subreddit
168
+ dun posts validate --accounts ACCOUNT_ID --meta '{"reddit":{…}}'
92
169
  dun drafts create --text "Idea for later"
93
170
  dun personalization get
94
171
  dun personalization set --description "Concise, direct, no fluff."
@@ -96,8 +173,9 @@ dun personalization set --description "Concise, direct, no fluff."
96
173
 
97
174
  Tips:
98
175
 
99
- - Help for any command: `dun posts --help`
176
+ - Help for any command: `dun posts --help` / `dun reddit --help`
100
177
  - Structured output for scripts: add `--json`
178
+ - Platform-specific options (Reddit, etc.): `--meta '{…}'`
101
179
  - Prefer ChatGPT or Claude **in the browser**? Use [MCP](https://docs.dunsocial.com/mcp) instead of the CLI
102
180
  - Personalization is workspace-wide AI voice (Settings → Personalization). Updating it needs a token with the `workspace:write` scope
103
181
 
package/dist/index.js CHANGED
@@ -1,26 +1,26 @@
1
1
  #!/usr/bin/env node
2
- import{readFileSync as IU}from"node:fs";import{mkdir as mU,readFile as _U,writeFile as MU,chmod as vU,unlink as hU}from"node:fs/promises";import{dirname as SU,join as n}from"node:path";import{homedir as pU}from"node:os";var Q={OK:0,USAGE:1,AUTH:2,FORBIDDEN:3,VALIDATION:4,NOT_FOUND:5,RATE_LIMITED:6,NETWORK:7};class Z extends Error{code;details;constructor(U,u=Q.USAGE,$){super(U);this.name="CliError",this.code=u,this.details=$}}function t(U){if(U===401)return Q.AUTH;if(U===403)return Q.FORBIDDEN;if(U===404)return Q.NOT_FOUND;if(U===429)return Q.RATE_LIMITED;if(U>=400&&U<500)return Q.VALIDATION;return Q.NETWORK}var nU="https://api.dunsocial.com",UU="0.9.0";function CU(){if(!UU.includes("@@"))return UU;try{let U=JSON.parse(IU(n(import.meta.dir,"..","package.json"),"utf8"));if(typeof U.version==="string"&&U.version.length>0)return U.version}catch{}return"0.0.0"}var B=CU();function v(){if(process.env.DUN_CONFIG)return process.env.DUN_CONFIG;let U=process.env.XDG_CONFIG_HOME,u=U?U:n(pU(),".config");return n(u,"dunsocial","config.json")}function j(){return v()}async function C(){let U=v();try{let u=await _U(U,"utf8");return JSON.parse(u)}catch(u){if(u.code==="ENOENT")return{};throw new Z(`Failed to read config at ${U}: ${u.message}`,Q.USAGE)}}async function I(U){let u=v();await mU(SU(u),{recursive:!0});let $={...U,updatedAt:new Date().toISOString()};await MU(u,`${JSON.stringify($,null,2)}
3
- `,"utf8");try{await vU(u,384)}catch{}}async function $U(){let U=v();try{await hU(U)}catch(u){if(u.code!=="ENOENT")throw new Z(`Failed to clear config: ${u.message}`,Q.USAGE)}}async function uU(U){let u=await C(),$=(U.apiUrl||process.env.DUN_API_URL||u.apiUrl||nU).replace(/\/$/,""),z=U.token||process.env.DUN_TOKEN||u.token,X=U.workspace||process.env.DUN_WORKSPACE_ID||u.defaultWorkspaceId||void 0,D=process.env.DUN_JSON==="0",O=!process.stdout.isTTY,w=D?!1:U.json===!0||process.env.DUN_JSON==="1"||process.env.CI==="true"||process.env.CI==="1"||O;return{apiUrl:$,token:z,workspaceId:X,json:w,debug:U.debug===!0||process.env.DUN_DEBUG==="1",quiet:U.quiet===!0,yes:U.yes===!0||process.env.CI==="true"||process.env.CI==="1",config:u}}function F(U){if(!U.token)throw new Z("Not authenticated. Run `dun auth login` (or `dun auth login --token <token>` / set DUN_TOKEN).",Q.AUTH);return U.token}function T(U){if(!U.workspaceId)throw new Z("No workspace selected. Run `dun workspace use <id|slug>` or pass --workspace / DUN_WORKSPACE_ID.",Q.VALIDATION);return U.workspaceId}function DU(U){let u={},$=[],z=0,X=!0;while(z<U.length){let Y=U[z];if(X&&Y==="--"){X=!1,z+=1;continue}if(X&&Y.startsWith("--")){let q=Y.indexOf("=");if(q!==-1){u[Y.slice(2,q)]=Y.slice(q+1),z+=1;continue}let y=Y.slice(2),W=U[z+1];if(W!==void 0&&!W.startsWith("-"))u[y]=W,z+=2;else u[y]=!0,z+=1;continue}if(X&&Y.startsWith("-")&&Y.length>1&&Y!=="-"){let q=Y.slice(1);if(q.length>1&&!q.includes("=")){for(let N of q)u[N]=!0;z+=1;continue}let y=q,W=U[z+1];if(W!==void 0&&!W.startsWith("-"))u[y]=W,z+=2;else u[y]=!0,z+=1;continue}$.push(Y),z+=1}let D=$[0],O,w,J=1;if(D==="memory"&&($[1]==="collections"||$[1]==="collection"))w="collections",O=$[2],J=O?3:2;else O=$[1],J=O?2:1;return{group:D,action:O,nest:w,flags:u,positionals:$.slice(J)}}function K(U,...u){for(let $ of u){let z=U[$];if(typeof z==="string"&&z.length>0)return z}return}function G(U,...u){for(let $ of u){let z=U[$];if(z===!0)return!0;if(typeof z==="string"){let X=z.toLowerCase();if(X==="1"||X==="true"||X==="yes")return!0}}return!1}function d(U,...u){let $=K(U,...u);if($===void 0)return;let z=Number($);if(!Number.isFinite(z))throw Error(`Invalid number for --${u[0]}: ${$}`);return z}function L(U,...u){let $=K(U,...u);if($===void 0)return;return $.split(",").map((z)=>z.trim()).filter(Boolean)}function E(U){return G(U,"help","h")}function f(U){return G(U,"version","v","V")}function V(U,u){if(u.quiet&&!u.json)return;if(u.json||!process.stdout.isTTY){process.stdout.write(`${JSON.stringify({ok:!0,data:U},null,2)}
4
- `);return}if(U===void 0||U===null){process.stdout.write(`ok
5
- `);return}if(typeof U==="string"){process.stdout.write(`${U}
6
- `);return}process.stdout.write(`${eU(U,u.emptyMessage)}
7
- `)}function OU(U,u){let{message:$,code:z,details:X}=fU(U);if(u.json||!process.stdout.isTTY)process.stdout.write(`${JSON.stringify({ok:!1,error:{code:EU(z),message:$,details:X??null}},null,2)}
8
- `);else if(process.stderr.write(`error: ${$}
9
- `),X!==void 0&&process.env.DUN_DEBUG==="1")process.stderr.write(`${JSON.stringify(X,null,2)}
10
- `);return z}function EU(U){switch(U){case Q.AUTH:return"auth";case Q.FORBIDDEN:return"forbidden";case Q.VALIDATION:return"validation";case Q.NOT_FOUND:return"not_found";case Q.RATE_LIMITED:return"rate_limited";case Q.NETWORK:return"network";default:return"usage"}}function fU(U){if(U instanceof Z)return{message:U.message,code:U.code,details:U.details};if(U instanceof Error)return{message:U.message,code:Q.USAGE};return{message:String(U),code:Q.USAGE}}function eU(U,u){if(Array.isArray(U)){if(U.length===0)return u??"(empty)";if(U.every(($)=>$&&typeof $==="object"))return wU(U,u);return U.map(($)=>`- ${e($)}`).join(`
11
- `)}if(U&&typeof U==="object"){let $=U;if(Array.isArray($.items)){if($.items.length===0&&u)return u;let z=typeof $.total==="number"?`total=${$.total} showing=${$.items.length}`:void 0,X=wU($.items,u);return z?`${z}
12
- ${X}`:X}return Object.entries($).map(([z,X])=>`${z}: ${e(X)}`).join(`
13
- `)}return String(U)}function wU(U,u){if(U.length===0)return u??"(empty)";let $=["id","name","slug","status","providerName","username","displayName","role","scheduledAt","content","text","originalFilename","score","collectionId"],z=new Set;for(let Y of U)for(let q of Object.keys(Y))z.add(q);let X=[...$.filter((Y)=>z.has(Y)),...[...z].filter((Y)=>!$.includes(Y)).slice(0,6)].slice(0,8),D=U.map((Y)=>X.map((q)=>iU(e(Y[q]),q==="content"||q==="text"?48:28))),O=X.map((Y,q)=>Math.max(Y.length,...D.map((y)=>y[q]?.length??0))),w=X.map((Y,q)=>Y.padEnd(O[q])).join(" "),J=D.map((Y)=>Y.map((q,y)=>q.padEnd(O[y])).join(" ")).join(`
14
- `);return`${w}
15
- ${J}`}function e(U){if(U===null||U===void 0)return"";if(typeof U==="string")return U.replace(/\s+/g," ").trim();if(typeof U==="number"||typeof U==="boolean")return String(U);if(U instanceof Date)return U.toISOString();if(typeof U==="object"){let u=U;if(typeof u.id==="string"&&typeof u.name==="string")return`${u.name} (${u.id})`;if(typeof u.status==="string")return u.status;return JSON.stringify(U)}return String(U)}function iU(U,u){if(U.length<=u)return U;return`${U.slice(0,Math.max(0,u-1))}...`}function m(U,u,$){if(!U)return;if($!==void 0){process.stderr.write(`[debug] ${u} ${JSON.stringify($)}
16
- `);return}process.stderr.write(`[debug] ${u}
17
- `)}function P(U,u){let $=`${U} ${u}`,z="-".repeat(Math.max(12,$.length+4));return`${$}
18
- ${z}`}var i=`dun - schedule & publish social posts
2
+ import{readFileSync as eu}from"node:fs";import{mkdir as Su,readFile as hu,writeFile as pu,chmod as fu,unlink as iu}from"node:fs/promises";import{dirname as Eu,join as f}from"node:path";import{homedir as Cu}from"node:os";var O={OK:0,USAGE:1,AUTH:2,FORBIDDEN:3,VALIDATION:4,NOT_FOUND:5,RATE_LIMITED:6,NETWORK:7};class z extends Error{code;details;constructor(u,w=O.USAGE,d){super(u);this.name="CliError",this.code=w,this.details=d}}function uu(u){if(u===401)return O.AUTH;if(u===403)return O.FORBIDDEN;if(u===404)return O.NOT_FOUND;if(u===429)return O.RATE_LIMITED;if(u>=400&&u<500)return O.VALIDATION;return O.NETWORK}var ou="https://api.dunsocial.com",du="0.10.0";function gu(){if(!du.includes("@@"))return du;try{let u=JSON.parse(eu(f(import.meta.dir,"..","package.json"),"utf8"));if(typeof u.version==="string"&&u.version.length>0)return u.version}catch{}return"0.0.0"}var T=gu();function L(){if(process.env.DUN_CONFIG)return process.env.DUN_CONFIG;let u=process.env.XDG_CONFIG_HOME,w=u?u:f(Cu(),".config");return f(w,"dunsocial","config.json")}function _(){return L()}async function i(){let u=L();try{let w=await hu(u,"utf8");return JSON.parse(w)}catch(w){if(w.code==="ENOENT")return{};throw new z(`Failed to read config at ${u}: ${w.message}`,O.USAGE)}}async function j(u){let w=L();await Su(Eu(w),{recursive:!0});let d={...u,updatedAt:new Date().toISOString()};await pu(w,`${JSON.stringify(d,null,2)}
3
+ `,"utf8");try{await fu(w,384)}catch{}}async function wu(){let u=L();try{await iu(u)}catch(w){if(w.code!=="ENOENT")throw new z(`Failed to clear config: ${w.message}`,O.USAGE)}}async function mu(u){let w=await i(),d=(u.apiUrl||process.env.DUN_API_URL||w.apiUrl||ou).replace(/\/$/,""),D=u.token||process.env.DUN_TOKEN||w.token,y=u.workspace||process.env.DUN_WORKSPACE_ID||w.defaultWorkspaceId||void 0,m=process.env.DUN_JSON==="0",U=!process.stdout.isTTY,$=m?!1:u.json===!0||process.env.DUN_JSON==="1"||process.env.CI==="true"||process.env.CI==="1"||U;return{apiUrl:d,token:D,workspaceId:y,json:$,debug:u.debug===!0||process.env.DUN_DEBUG==="1",quiet:u.quiet===!0,yes:u.yes===!0||process.env.CI==="true"||process.env.CI==="1",config:w}}function Y(u){if(!u.token)throw new z("Not authenticated. Run `dun auth login` (or `dun auth login --token <token>` / set DUN_TOKEN).",O.AUTH);return u.token}function F(u){if(!u.workspaceId)throw new z("No workspace selected. Run `dun workspace use <id|slug>` or pass --workspace / DUN_WORKSPACE_ID.",O.VALIDATION);return u.workspaceId}function $u(u){let w={},d=[],D=0,y=!0;while(D<u.length){let X=u[D];if(y&&X==="--"){y=!1,D+=1;continue}if(y&&X.startsWith("--")){let Z=X.indexOf("=");if(Z!==-1){w[X.slice(2,Z)]=X.slice(Z+1),D+=1;continue}let Q=X.slice(2),J=u[D+1];if(J!==void 0&&!J.startsWith("-"))w[Q]=J,D+=2;else w[Q]=!0,D+=1;continue}if(y&&X.startsWith("-")&&X.length>1&&X!=="-"){let Z=X.slice(1);if(Z.length>1&&!Z.includes("=")){for(let G of Z)w[G]=!0;D+=1;continue}let Q=Z,J=u[D+1];if(J!==void 0&&!J.startsWith("-"))w[Q]=J,D+=2;else w[Q]=!0,D+=1;continue}d.push(X),D+=1}let m=d[0],U,$,q=1;if(m==="memory"&&(d[1]==="collections"||d[1]==="collection"))$="collections",U=d[2],q=U?3:2;else U=d[1],q=U?2:1;return{group:m,action:U,nest:$,flags:w,positionals:d.slice(q)}}function V(u,...w){for(let d of w){let D=u[d];if(typeof D==="string"&&D.length>0)return D}return}function R(u,...w){for(let d of w){let D=u[d];if(D===!0)return!0;if(typeof D==="string"){let y=D.toLowerCase();if(y==="1"||y==="true"||y==="yes")return!0}}return!1}function B(u,...w){let d=V(u,...w);if(d===void 0)return;let D=Number(d);if(!Number.isFinite(D))throw Error(`Invalid number for --${w[0]}: ${d}`);return D}function I(u,...w){let d=V(u,...w);if(d===void 0)return;return d.split(",").map((D)=>D.trim()).filter(Boolean)}function E(u){return R(u,"help","h")}function C(u){return R(u,"version","v","V")}function k(u,w){if(w.quiet&&!w.json)return;if(w.json||!process.stdout.isTTY){process.stdout.write(`${JSON.stringify({ok:!0,data:u},null,2)}
4
+ `);return}if(u===void 0||u===null){process.stdout.write(`ok
5
+ `);return}if(typeof u==="string"){process.stdout.write(`${u}
6
+ `);return}process.stdout.write(`${cu(u,w.emptyMessage)}
7
+ `)}function Du(u,w){let{message:d,code:D,details:y}=ru(u);if(w.json||!process.stdout.isTTY)process.stdout.write(`${JSON.stringify({ok:!1,error:{code:su(D),message:d,details:y??null}},null,2)}
8
+ `);else if(process.stderr.write(`error: ${d}
9
+ `),y!==void 0&&process.env.DUN_DEBUG==="1")process.stderr.write(`${JSON.stringify(y,null,2)}
10
+ `);return D}function su(u){switch(u){case O.AUTH:return"auth";case O.FORBIDDEN:return"forbidden";case O.VALIDATION:return"validation";case O.NOT_FOUND:return"not_found";case O.RATE_LIMITED:return"rate_limited";case O.NETWORK:return"network";default:return"usage"}}function ru(u){if(u instanceof z)return{message:u.message,code:u.code,details:u.details};if(u instanceof Error)return{message:u.message,code:O.USAGE};return{message:String(u),code:O.USAGE}}function cu(u,w){if(Array.isArray(u)){if(u.length===0)return w??"(empty)";if(u.every((d)=>d&&typeof d==="object"))return Uu(u,w);return u.map((d)=>`- ${o(d)}`).join(`
11
+ `)}if(u&&typeof u==="object"){let d=u;if(Array.isArray(d.items)){if(d.items.length===0&&w)return w;let D=typeof d.total==="number"?`total=${d.total} showing=${d.items.length}`:void 0,y=Uu(d.items,w);return D?`${D}
12
+ ${y}`:y}return Object.entries(d).map(([D,y])=>`${D}: ${o(y)}`).join(`
13
+ `)}return String(u)}function Uu(u,w){if(u.length===0)return w??"(empty)";let d=["id","name","slug","status","providerName","username","displayName","role","scheduledAt","content","text","originalFilename","score","collectionId"],D=new Set;for(let X of u)for(let Z of Object.keys(X))D.add(Z);let y=[...d.filter((X)=>D.has(X)),...[...D].filter((X)=>!d.includes(X)).slice(0,6)].slice(0,8),m=u.map((X)=>y.map((Z)=>xu(o(X[Z]),Z==="content"||Z==="text"?48:28))),U=y.map((X,Z)=>Math.max(X.length,...m.map((Q)=>Q[Z]?.length??0))),$=y.map((X,Z)=>X.padEnd(U[Z])).join(" "),q=m.map((X)=>X.map((Z,Q)=>Z.padEnd(U[Q])).join(" ")).join(`
14
+ `);return`${$}
15
+ ${q}`}function o(u){if(u===null||u===void 0)return"";if(typeof u==="string")return u.replace(/\s+/g," ").trim();if(typeof u==="number"||typeof u==="boolean")return String(u);if(u instanceof Date)return u.toISOString();if(typeof u==="object"){let w=u;if(typeof w.id==="string"&&typeof w.name==="string")return`${w.name} (${w.id})`;if(typeof w.status==="string")return w.status;return JSON.stringify(u)}return String(u)}function xu(u,w){if(u.length<=w)return u;return`${u.slice(0,Math.max(0,w-1))}...`}function P(u,w,d){if(!u)return;if(d!==void 0){process.stderr.write(`[debug] ${w} ${JSON.stringify(d)}
16
+ `);return}process.stderr.write(`[debug] ${w}
17
+ `)}function W(u,w){let d=`${u} ${w}`,D="-".repeat(Math.max(12,d.length+4));return`${d}
18
+ ${D}`}var g=`dun - schedule & publish social posts
19
19
 
20
20
  Usage:
21
21
  dun <command> [flags]
22
22
 
23
- ${P("\uD83D\uDD10","Auth")}
23
+ ${W("\uD83D\uDD10","Auth")}
24
24
  dun auth login # device-code browser flow
25
25
  dun auth login --token <token> # CI / headless
26
26
  dun auth login --no-browser
@@ -28,38 +28,51 @@ ${P("\uD83D\uDD10","Auth")}
28
28
  dun auth whoami
29
29
  dun auth status
30
30
 
31
- ${P("\uD83D\uDCC1","Workspaces")}
31
+ ${W("\uD83D\uDCC1","Workspaces")}
32
32
  dun workspace list
33
33
  dun workspace use <id|slug>
34
34
  dun workspace current
35
35
 
36
- ${P("\uD83D\uDD17","Accounts")}
37
- dun accounts list [--platform x]
36
+ ${W("\uD83D\uDD17","Accounts")}
37
+ dun accounts list [--platform x|reddit|...]
38
38
 
39
- ${P("\uD83D\uDCDD","Posts")}
39
+ ${W("\uD83D\uDFE0","Reddit")}
40
+ dun reddit flairs <subreddit> # template id + text + textEditable
41
+ dun reddit requirements <subreddit> # live post rules (flair required, ...)
42
+ # alias: dun accounts reddit flairs <subreddit>
43
+
44
+ ${W("\uD83D\uDCDD","Posts")}
40
45
  dun posts list [--status scheduled|published|failed|draft|cancelled]
41
46
  dun posts get <id>
42
- dun posts schedule --text "..." --accounts <id,id> (--at ISO | --in 2h) [--media id,id]
43
- dun posts publish --text "..." --accounts <id,id> [--media id,id] [--natural]
47
+ dun posts validate --accounts <id> [--media id] --meta '{...}'
48
+ dun posts schedule --text "..." --accounts <id,id> (--at ISO | --in 2h) [--media id,id] [--meta '{...}']
49
+ dun posts publish --text "..." --accounts <id,id> [--media id,id] [--meta '{...}'] [--natural] [--wait]
44
50
  dun posts reschedule <id> --at ISO|--in 2h [--text "..."]
45
51
  dun posts cancel <id>
46
52
  dun posts delete <id> [--yes]
47
53
  dun posts x-cap
48
54
 
49
- ${P("\uD83D\uDCC4","Drafts")}
55
+ # Reddit --meta example (video):
56
+ # --meta '{"reddit":{"subreddit":"tamilyapping","kind":"video","title":"...","flairId":"..."}}'
57
+ # kinds: self | link | image | video
58
+ # Personal LinkedIn video (no --meta): upload one mp4/mov, then --media ASSET on a LinkedIn account
59
+ # Company Pages stay images-only. Do not mix video + images on LinkedIn.
60
+ # publish queues async work; without --wait, status scheduled means queued
61
+
62
+ ${W("\uD83D\uDCC4","Drafts")}
50
63
  dun drafts list
51
64
  dun drafts get <id>
52
65
  dun drafts create --text "..." [--platforms x,linkedin] [--accounts id,id]
53
66
  dun drafts update <id> --text "..."
54
67
  dun drafts delete <id> [--yes]
55
68
 
56
- ${P("\uD83D\uDDBC️","Media")}
69
+ ${W("\uD83D\uDDBC️","Media")}
57
70
  dun media list
58
71
  dun media get <id>
59
- dun media upload <file> [--alt "..."]
72
+ dun media upload <file> [--alt "..."] # local preflight: images <=8MB, video <=256MB
60
73
  dun media delete <id> [--yes]
61
74
 
62
- ${P("\uD83E\uDDE0","Memory")}
75
+ ${W("\uD83E\uDDE0","Memory")}
63
76
  dun memory collections list
64
77
  dun memory collections create --name "..." [--color blue] [--private]
65
78
  dun memory list --collection <id>
@@ -67,7 +80,7 @@ ${P("\uD83E\uDDE0","Memory")}
67
80
  dun memory search --prompt "..." --collections <id,id> [--top-k 5]
68
81
  dun memory delete <id> --collection <id> [--yes]
69
82
 
70
- ${P("\uD83C\uDF99️","Personalization")}
83
+ ${W("\uD83C\uDF99️","Personalization")}
71
84
  # workspace AI voice
72
85
  dun personalization get
73
86
  dun personalization set --description "..."
@@ -75,21 +88,21 @@ ${P("\uD83C\uDF99️","Personalization")}
75
88
  dun personalization set --clear
76
89
  # requires PAT scope workspace:write for set (owner/admin)
77
90
 
78
- ${P("\uD83E\uDDE9","Agent skills")}
91
+ ${W("\uD83E\uDDE9","Agent skills")}
79
92
  dun skill install <agent|all>
80
93
  dun skill list
81
94
  dun skill path
82
95
  # agents: claude codex cursor pi opencode agents copilot windsurf
83
96
  # goose gemini amp grok continue antigravity
84
97
 
85
- ${P("⬆️","Update")}
98
+ ${W("⬆️","Update")}
86
99
  dun update # upgrade to latest from npm
87
100
  dun update --check # report only
88
101
  dun update --force # reinstall latest even if current
89
102
  # aliases: upgrade, self-update
90
103
  # detects npm / bun / pnpm / yarn; prints command for npx
91
104
 
92
- ${P("⚙️","Global flags")}
105
+ ${W("⚙️","Global flags")}
93
106
  --json Machine-readable output (default when non-TTY / CI)
94
107
  --workspace <id> Override workspace for this command
95
108
  --api-url <url> Override API base (default https://api.dunsocial.com)
@@ -100,14 +113,14 @@ ${P("⚙️","Global flags")}
100
113
  --help, -h Show help
101
114
  --version, -v Show version
102
115
 
103
- ${P("\uD83C\uDF31","Env")}
116
+ ${W("\uD83C\uDF31","Env")}
104
117
  DUN_TOKEN DUN_WORKSPACE_ID DUN_API_URL DUN_CONFIG DUN_JSON=1 DUN_DEBUG=1
105
118
 
106
- ${P("\uD83D\uDEAA","Exit codes")}
119
+ ${W("\uD83D\uDEAA","Exit codes")}
107
120
  0 ok | 1 usage | 2 auth | 3 forbidden | 4 validation | 5 not found | 6 rate limit | 7 network
108
121
 
109
122
  Docs: https://dunsocial.com/docs/cli
110
- `;function g(U){return{auth:`dun auth
123
+ `;function s(u){return{auth:`dun auth
111
124
 
112
125
  login [--token] Device-code browser login (default) or --token for CI
113
126
  logout Clear local config
@@ -118,17 +131,59 @@ Docs: https://dunsocial.com/docs/cli
118
131
  list List workspaces you belong to
119
132
  use <id|slug> Set default workspace
120
133
  current Show default workspace
121
- `,workspaces:"Alias of workspace",accounts:"dun accounts list [--platform <name>]",posts:`dun posts
134
+ `,workspaces:"Alias of workspace",accounts:`dun accounts list [--platform <name>]
135
+ dun accounts reddit flairs <subreddit> # alias of dun reddit flairs
136
+ `,reddit:`dun reddit
137
+
138
+ flairs <subreddit> List flair template ids for the connected Reddit account
139
+ requirements <subreddit> Live post requirements (flair required, title limits, ...)
140
+
141
+ Output fields for flairs: id (flair_template_id), text, textEditable
142
+
143
+ Example:
144
+ dun reddit flairs tamilyapping --json
145
+ `,posts:`dun posts
122
146
 
123
147
  list [--status] [--account] [--limit] [--offset]
124
148
  get <id>
125
- schedule --text --accounts (--at|--in) [--media]
126
- publish --text --accounts [--media] [--natural]
149
+ validate --accounts [--text] [--media] --meta Dry-run Reddit/media rules before enqueue
150
+ schedule --text --accounts (--at|--in) [--media] [--meta]
151
+ publish --text --accounts [--media] [--meta] [--natural] [--wait] [--wait-timeout 120]
127
152
  reschedule <id> (--at|--in) [--text]
128
153
  cancel <id>
129
154
  delete <id> [--yes]
130
155
  x-cap
131
- `,drafts:"dun drafts list|get|create|update|delete",media:"dun media list|get|upload|delete",memory:`dun memory collections list|create
156
+
157
+ Platform metadata (--meta / --metadata / --meta-json):
158
+ Reddit (required when publishing to a Reddit account):
159
+ {"reddit":{"subreddit":"NAME","kind":"self|link|image|video","title":"...","flairId":"..."}}
160
+ Optional reddit fields: body, url (link), flairText (only if textEditable), posterMediaAssetId, nsfw, spoiler
161
+
162
+ Reddit happy path:
163
+ 1. dun media upload ./video.mp4 --json # rejects >256MB locally
164
+ 2. dun reddit flairs SUB --json # pick flairId
165
+ 3. dun posts validate --accounts ID --media ASSET --meta '...' --json
166
+ 4. dun posts publish --accounts ID --media ASSET --meta '...' --wait --json
167
+
168
+ Personal LinkedIn video (no --meta):
169
+ 1. dun accounts list --platform linkedin --json # personal profile id (not linkedin-business)
170
+ 2. dun media upload ./clip.mp4 --json # mp4 or mov; <=256MB
171
+ 3. dun posts publish --accounts ID --text "..." --media ASSET --wait --json
172
+ Rules: exactly one video OR images (not mixed). Company Pages are images-only.
173
+
174
+ Notes:
175
+ - publish without --wait returns status scheduled (queued). Poll get or use --wait.
176
+ - Prefer flairId alone. flairText without flairId always fails.
177
+ `,drafts:"dun drafts list|get|create|update|delete",media:`dun media
178
+
179
+ list|get|upload|delete
180
+ upload <file> [--alt] [--mime]
181
+
182
+ Local preflight before upload-url:
183
+ images <= 8MB (jpeg/png/gif/webp)
184
+ video <= 256MB (mp4/mov/webm)
185
+ Oversized video error includes an ffmpeg compress one-liner.
186
+ `,memory:`dun memory collections list|create
132
187
  dun memory list|save|search|delete`,personalization:`dun personalization
133
188
 
134
189
  get Show workspace AI voice profile
@@ -147,41 +202,43 @@ dun skill path`,update:`dun update
147
202
  --force Reinstall latest even if versions match
148
203
 
149
204
  Aliases: dun upgrade, dun self-update
150
- `,upgrade:"Alias of update","self-update":"Alias of update"}[U]??i}var gU=["DunSocial","---------"].join(`
151
- `),zU="DunSocial";var C$=QU(gU),E$=QU(zU);function QU(U){return Math.max(...U.split(`
152
- `).map((u)=>u.length))}function A(U=process.stderr){if(!U.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 XU(){return`DunSocial CLI v${B}`}function ZU(){return`${zU}
153
- CLI v${B}`}var oU="https://dunsocial.com/docs/cli";function xU(U){if(!U.token)return"signed_out";if(!U.workspaceId)return"needs_workspace";return"ready"}function JU(U){let u=xU(U),$=[`DunSocial CLI v${B}`,""];if(u==="signed_out")$.push("Not signed in.",""," Next: dun auth login");else if(u==="needs_workspace")$.push("Signed in — pick a workspace.",""," Next: dun workspace list"," dun workspace use <slug>");else{let z=U.config.defaultWorkspaceSlug||U.config.defaultWorkspaceName||U.workspaceId||"workspace";$.push(`Ready | workspace: ${z}`,""," Tip: dun accounts list"," dun posts list")}return $.push(""," dun --help all commands",` Docs ${oU}`,""),$.join(`
154
- `)}import{spawn as cU}from"node:child_process";class VU{ctx;constructor(U){this.ctx=U}async request(U){let u=U.method??"GET",$=new URL(U.path.startsWith("http")?U.path:`${this.ctx.apiUrl}${U.path}`);if(U.query)for(let[J,Y]of Object.entries(U.query)){if(Y===void 0||Y===null||Y==="")continue;$.searchParams.set(J,String(Y))}let z={Accept:"application/json","User-Agent":`dunsocial-cli/${B}`,...U.headers??{}};if(U.auth!==!1)z.Authorization=`Bearer ${F(this.ctx)}`;if(U.workspace)z["X-Workspace-Id"]=T(this.ctx);let X;if(U.body!==void 0)z["Content-Type"]="application/json",X=JSON.stringify(U.body);m(this.ctx.debug,`${u} ${$.toString()}`);let D;try{D=await fetch($,{method:u,headers:z,body:X})}catch(J){throw new Z(`Network error: ${J.message}`,Q.NETWORK,{cause:String(J)})}let O=await D.text(),w=null;if(O)try{w=JSON.parse(O)}catch{w=null}if(m(this.ctx.debug,`<- ${D.status}`,{ok:D.ok,bodyPreview:O.slice(0,300)}),!D.ok){let J=w?.error||w?.message||(O?O.slice(0,300):`HTTP ${D.status}`);throw new Z(J,t(D.status),{status:D.status,body:w??O})}if(w&&typeof w==="object"&&"success"in w){if(w.success===!1)throw new Z(w.error||w.message||"Request failed",Q.VALIDATION,w);return w.data}return w??void 0}get(U,u){return this.request({...u,method:"GET",path:U})}post(U,u,$){return this.request({...$,method:"POST",path:U,body:u})}patch(U,u,$){return this.request({...$,method:"PATCH",path:U,body:u})}delete(U,u,$){return this.request({...$,method:"DELETE",path:U,body:u})}}function k(U){return new VU(U)}async function YU(U,u,$,z){switch(u){case"login":return rU(U,$);case"logout":return aU(U);case"whoami":return tU(U);case"status":return U$(U);default:throw new Z(`Unknown auth command "${u??""}". Try: login, logout, whoami, status`,Q.USAGE)}}async function rU(U,u){let $=K(u,"token")||process.env.DUN_TOKEN;if($)return sU(U,$);return lU(U,u)}function h(U){if(U)return"Logged in.";return`Logged in.
155
- Next: dun workspace list && dun workspace use <id|slug>`}async function sU(U,u){let $={...U,token:u},X=await k($).get("/api/user/profile");if(await I({...U.config,apiUrl:U.apiUrl,token:u,defaultWorkspaceId:U.config.defaultWorkspaceId,defaultWorkspaceSlug:U.config.defaultWorkspaceSlug,defaultWorkspaceName:U.config.defaultWorkspaceName}),!U.json&&A(process.stdout)){V(h(Boolean(U.config.defaultWorkspaceId)),U);return}V({user:{id:X.id,name:X.name,email:X.email},configPath:j(),method:"token",message:h(Boolean(U.config.defaultWorkspaceId))},U)}async function lU(U,u){let $=k({...U,token:void 0}),z=await $.post("/api/cli/auth/start",{clientName:"dun-cli"},{auth:!1}),X=!G(u,"no-browser");if(!U.json){if(A(process.stderr))process.stderr.write(`
156
- ${ZU()}
205
+ `,upgrade:"Alias of update","self-update":"Alias of update"}[u]??g}var au=["DunSocial","---------"].join(`
206
+ `),Ou="DunSocial";var xd=yu(au),ad=yu(Ou);function yu(u){return Math.max(...u.split(`
207
+ `).map((w)=>w.length))}function H(u=process.stderr){if(!u.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 zu(){return`DunSocial CLI v${T}`}function qu(){return`${Ou}
208
+ CLI v${T}`}var lu="https://dunsocial.com/docs/cli";function tu(u){if(!u.token)return"signed_out";if(!u.workspaceId)return"needs_workspace";return"ready"}function Xu(u){let w=tu(u),d=[`DunSocial CLI v${T}`,""];if(w==="signed_out")d.push("Not signed in.",""," Next: dun auth login");else if(w==="needs_workspace")d.push("Signed in — pick a workspace.",""," Next: dun workspace list"," dun workspace use <slug>");else{let D=u.config.defaultWorkspaceSlug||u.config.defaultWorkspaceName||u.workspaceId||"workspace";d.push(`Ready | workspace: ${D}`,""," Tip: dun accounts list"," dun posts list")}return d.push(""," dun --help all commands",` Docs ${lu}`,""),d.join(`
209
+ `)}import{spawn as ud}from"node:child_process";class Zu{ctx;constructor(u){this.ctx=u}async request(u){let w=u.method??"GET",d=new URL(u.path.startsWith("http")?u.path:`${this.ctx.apiUrl}${u.path}`);if(u.query)for(let[q,X]of Object.entries(u.query)){if(X===void 0||X===null||X==="")continue;d.searchParams.set(q,String(X))}let D={Accept:"application/json","User-Agent":`dunsocial-cli/${T}`,...u.headers??{}};if(u.auth!==!1)D.Authorization=`Bearer ${Y(this.ctx)}`;if(u.workspace)D["X-Workspace-Id"]=F(this.ctx);let y;if(u.body!==void 0)D["Content-Type"]="application/json",y=JSON.stringify(u.body);P(this.ctx.debug,`${w} ${d.toString()}`);let m;try{m=await fetch(d,{method:w,headers:D,body:y})}catch(q){throw new z(`Network error: ${q.message}`,O.NETWORK,{cause:String(q)})}let U=await m.text(),$=null;if(U)try{$=JSON.parse(U)}catch{$=null}if(P(this.ctx.debug,`<- ${m.status}`,{ok:m.ok,bodyPreview:U.slice(0,300)}),!m.ok){let q=$?.error||$?.message||(U?U.slice(0,300):`HTTP ${m.status}`);throw new z(q,uu(m.status),{status:m.status,body:$??U})}if($&&typeof $==="object"&&"success"in $){if($.success===!1)throw new z($.error||$.message||"Request failed",O.VALIDATION,$);return $.data}return $??void 0}get(u,w){return this.request({...w,method:"GET",path:u})}post(u,w,d){return this.request({...d,method:"POST",path:u,body:w})}patch(u,w,d){return this.request({...d,method:"PATCH",path:u,body:w})}delete(u,w,d){return this.request({...d,method:"DELETE",path:u,body:w})}}function b(u){return new Zu(u)}async function ku(u,w,d,D){switch(w){case"login":return dd(u,d);case"logout":return $d(u);case"whoami":return Ud(u);case"status":return Dd(u);default:throw new z(`Unknown auth command "${w??""}". Try: login, logout, whoami, status`,O.USAGE)}}async function dd(u,w){let d=V(w,"token")||process.env.DUN_TOKEN;if(d)return wd(u,d);return md(u,w)}function v(u){if(u)return"Logged in.";return`Logged in.
210
+ Next: dun workspace list && dun workspace use <id|slug>`}async function wd(u,w){let d={...u,token:w},y=await b(d).get("/api/user/profile");if(await j({...u.config,apiUrl:u.apiUrl,token:w,defaultWorkspaceId:u.config.defaultWorkspaceId,defaultWorkspaceSlug:u.config.defaultWorkspaceSlug,defaultWorkspaceName:u.config.defaultWorkspaceName}),!u.json&&H(process.stdout)){k(v(Boolean(u.config.defaultWorkspaceId)),u);return}k({user:{id:y.id,name:y.name,email:y.email},configPath:_(),method:"token",message:v(Boolean(u.config.defaultWorkspaceId))},u)}async function md(u,w){let d=b({...u,token:void 0}),D=await d.post("/api/cli/auth/start",{clientName:"dun-cli"},{auth:!1}),y=!R(w,"no-browser");if(!u.json){if(H(process.stderr))process.stderr.write(`
211
+ ${qu()}
157
212
 
158
213
  `);else process.stderr.write(`
159
214
  DunSocial CLI login
160
215
  `),process.stderr.write(`───────────────────
161
216
  `);process.stderr.write(`In your browser, open:
162
- ${z.verificationUrlComplete}
217
+ ${D.verificationUrlComplete}
163
218
 
164
- `),process.stderr.write(`Or go to ${z.verificationUrl} and enter code:
165
- `),process.stderr.write(` ${z.userCode}
219
+ `),process.stderr.write(`Or go to ${D.verificationUrl} and enter code:
220
+ `),process.stderr.write(` ${D.userCode}
166
221
 
167
222
  `),process.stderr.write(`Waiting for approval...
168
- `)}else process.stderr.write(JSON.stringify({ok:!0,phase:"waiting",userCode:z.userCode,verificationUrlComplete:z.verificationUrlComplete})+`
169
- `);if(X&&process.stdout.isTTY)try{await u$(z.verificationUrlComplete)}catch{}let D=Math.max(3,z.interval||5)*1000,O=Date.now()+(z.expiresIn||900)*1000;while(Date.now()<O){await $$(D);let w=await $.post("/api/cli/auth/poll",{deviceCode:z.deviceCode},{auth:!1});if(w.status==="pending")continue;if(w.status==="expired")throw new Z("Login expired. Run dun auth login again.",Q.AUTH);if(w.status==="denied")throw new Z("Login was denied in the browser.",Q.AUTH);if(w.status==="approved"){let J=w.token,Y={...U,token:J},q=k(Y),y=await q.get("/api/user/profile"),W={...U.config,apiUrl:U.apiUrl,token:J,defaultWorkspaceId:w.workspaceId||U.config.defaultWorkspaceId,defaultWorkspaceSlug:U.config.defaultWorkspaceSlug,defaultWorkspaceName:U.config.defaultWorkspaceName};if(w.workspaceId)try{let M=(await q.get("/api/workspaces")).find((jU)=>jU.id===w.workspaceId);if(M)W.defaultWorkspaceId=M.id,W.defaultWorkspaceSlug=M.slug,W.defaultWorkspaceName=M.name}catch{}await I(W);let N=Boolean(W.defaultWorkspaceId);if(!U.json&&A(process.stdout)){V(h(N),U);return}V({user:{id:y.id,name:y.name,email:y.email},workspaceId:W.defaultWorkspaceId??null,configPath:j(),method:"device",message:h(N)},U);return}}throw new Z("Login timed out. Run dun auth login again.",Q.AUTH)}async function aU(U){if(await $U(),!U.json&&A(process.stdout)){V("Logged out. Config cleared.",U);return}V({message:"Logged out. Config cleared.",configPath:j()},U)}async function tU(U){F(U);let $=await k(U).get("/api/user/profile");V({id:$.id,name:$.name,email:$.email,image:$.image??null,apiUrl:U.apiUrl,workspaceId:U.workspaceId??null},U)}async function U$(U){let u=Boolean(U.token),$=null,z=null;if(u)try{$=await k(U).get("/api/user/profile")}catch(X){z=X instanceof Error?X.message:String(X)}V({authenticated:Boolean($),tokenPresent:u,apiUrl:U.apiUrl,workspaceId:U.workspaceId??null,workspaceSlug:U.config.defaultWorkspaceSlug??null,workspaceName:U.config.defaultWorkspaceName??null,configPath:j(),user:$?{id:$.id,name:$.name,email:$.email}:null,error:z},U)}function $$(U){return new Promise((u)=>setTimeout(u,U))}async function u$(U){let u=process.platform,$=u==="darwin"?["open",U]:u==="win32"?["cmd","/c","start","",U]:["xdg-open",U];await new Promise((z,X)=>{let D=cU($[0],$.slice(1),{stdio:"ignore",windowsHide:!0});D.on("error",X),D.on("close",()=>z())})}async function qU(U,u,$,z){F(U);let X=k(U);switch(u){case"list":case"ls":{let D=await X.get("/api/workspaces");V(D.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})),{...U,emptyMessage:"No workspaces yet."});return}case"use":{let D=z[0];if(!D)throw new Z("Usage: dun workspace use <id|slug>",Q.USAGE);let w=(await X.get("/api/workspaces")).find((J)=>J.id===D||J.slug===D);if(!w)throw new Z(`Workspace not found: ${D}. Run dun workspace list`,Q.NOT_FOUND);await I({...U.config,apiUrl:U.apiUrl,token:U.token,defaultWorkspaceId:w.id,defaultWorkspaceSlug:w.slug,defaultWorkspaceName:w.name}),V({id:w.id,name:w.name,slug:w.slug,role:w.role,message:`Default workspace set to ${w.name}`},U);return}case"current":{if(!U.workspaceId&&!U.config.defaultWorkspaceId)throw new Z("No workspace selected. Run dun workspace use <id|slug>",Q.VALIDATION);let D=U.workspaceId||U.config.defaultWorkspaceId;try{let w=(await X.get("/api/workspaces")).find((J)=>J.id===D||J.slug===D);if(w){V({id:w.id,name:w.name,slug:w.slug,role:w.role,timezone:w.timezone},U);return}}catch{}V({id:D,name:U.config.defaultWorkspaceName??null,slug:U.config.defaultWorkspaceSlug??null},U);return}default:throw new Z(`Unknown workspace command "${u??""}". Try: list, use, current`,Q.USAGE)}}async function KU(U,u,$,z){F(U),T(U);let X=k(U);switch(u){case void 0:case"list":case"ls":{let D=await X.get("/api/social-accounts",{workspace:!0}),O=K($,"platform","p")?.toLowerCase(),w=O?D.filter((J)=>{let Y=(J.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((W)=>Y.includes(W))}):D;V(w.map((J)=>({id:J.id,providerName:J.providerName,username:J.username??null,displayName:J.displayName??null,isConnected:J.isConnected,expiresAt:J.expiresAt??null})),{...U,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 Z(`Unknown accounts command "${u}". Try: list`,Q.USAGE)}}import{readFileSync as O$}from"node:fs";var D$=/^(\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 o(U,u=new Date){let $=U.trim();if(!$)throw new Z("Empty time value",Q.VALIDATION);let z=$.match(D$);if(z){let O=Number(z[1]),w=z[2].toLowerCase(),J=w$(w)*O;return new Date(u.getTime()+J)}let X=$.match(/^in\s+(.+)$/i);if(X)return o(X[1],u);let D=new Date($);if(Number.isNaN(D.getTime()))throw new Z(`Invalid time "${U}". Use ISO-8601 (2026-06-01T09:00:00Z) or relative (2h, 30m, 1d).`,Q.VALIDATION);return D}function w$(U){switch(U){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 Z(`Unknown time unit: ${U}`,Q.VALIDATION)}}function S(U){if(U.at&&U.in)throw new Z("Pass only one of --at or --in",Q.VALIDATION);if(U.at)return o(U.at).toISOString();if(U.in)return o(U.in).toISOString();throw new Z("Schedule time required. Pass --at <iso> or --in <duration> (e.g. 2h).",Q.VALIDATION)}function WU(U){if(!U)return[];return U.split(",").map((u)=>u.trim()).filter(Boolean)}function yU(U){let u=K(U,"text","content","c","t");if(!u)throw new Z("Missing --text",Q.VALIDATION);return u}function x(U){let u=L(U,"accounts","account","a")??WU(K(U,"accounts","account","a"));if(!u||u.length===0)throw new Z("Missing --accounts <id,id>",Q.VALIDATION);return u}function c(U){return L(U,"media","media-urls")??WU(K(U,"media","media-urls"))}function _(U){let u=K(U,"meta","metadata","meta-json");if(!u)return;try{let $=JSON.parse(u);if(!$||typeof $!=="object"||Array.isArray($))throw Error("metadata must be a JSON object");return $}catch($){throw new Z(`Invalid --meta JSON: ${$.message}`,Q.VALIDATION)}}async function GU(U,u,$,z){F(U),T(U);let X=k(U);switch(u){case"list":case"ls":{let D=await X.get("/api/posts",{workspace:!0,query:{status:K($,"status","s"),socialAccountId:K($,"account","social-account"),groupId:K($,"group"),limit:d($,"limit")??50,offset:d($,"offset")??0}});V(D,{...U,emptyMessage:"No posts yet. Schedule one with dun posts schedule."});return}case"get":{let D=z[0];if(!D)throw new Z("Usage: dun posts get <id>",Q.USAGE);let O=await X.get(`/api/posts/${D}`,{workspace:!0});V(O,U);return}case"schedule":{let D=yU($),O=x($),w=S({at:K($,"at"),in:K($,"in")}),J=await X.post("/api/posts/schedule",{content:D,socialAccountIds:O,scheduledAt:w,mediaUrls:c($),metadata:_($)??{}},{workspace:!0});V(J,U);return}case"publish":case"publish-now":{let D=yU($),O=x($),w=await X.post("/api/posts/publish-now",{content:D,socialAccountIds:O,mediaUrls:c($),metadata:_($)??{},naturalPosting:G($,"natural","natural-posting")},{workspace:!0});V(w,U);return}case"reschedule":{let D=z[0];if(!D)throw new Z("Usage: dun posts reschedule <id> --at|--in",Q.USAGE);let w={scheduledAt:S({at:K($,"at"),in:K($,"in")})},J=K($,"text","content","c","t");if(J)w.content=J;let Y=c($);if(Y.length)w.mediaUrls=Y;let q=_($);if(q)w.metadata=q;if(!w.content){let W=await X.get(`/api/posts/${D}`,{workspace:!0});if(w.content=W.content,!w.metadata&&W.metadata)w.metadata=W.metadata;if(!w.mediaUrls&&W.mediaUrls)w.mediaUrls=W.mediaUrls}let y=await X.post(`/api/posts/${D}/reschedule`,w,{workspace:!0});V(y,U);return}case"cancel":{let D=z[0];if(!D)throw new Z("Usage: dun posts cancel <id>",Q.USAGE);let O=await X.post(`/api/posts/${D}/cancel`,void 0,{workspace:!0});V(O??{id:D,status:"cancelled"},U);return}case"delete":case"rm":{let D=z[0];if(!D)throw new Z("Usage: dun posts delete <id> [--yes]",Q.USAGE);if(!U.yes&&!G($,"yes","y"))throw new Z("Refusing to delete without --yes (or CI/yes mode)",Q.VALIDATION);let O=await X.delete(`/api/posts/${D}`,void 0,{workspace:!0});V(O??{id:D,deleted:!0},U);return}case"x-cap":case"xcap":{let D=await X.get("/api/posts/x-cap-usage",{workspace:!0});V(D,U);return}case"schedule-thread":case"publish-thread":case"publish-thread-now":{let D=K($,"file","f"),O=K($,"account","accounts","a")??x($)[0];if(!O)throw new Z("Missing --account <xAccountId>",Q.VALIDATION);let w;if(D){let Y;try{Y=O$(D,"utf8")}catch(y){throw new Z(`Could not read file: ${y.message}`,Q.VALIDATION)}let q=JSON.parse(Y);if(Array.isArray(q))w=q;else if(q&&typeof q==="object"&&Array.isArray(q.tweets))w=q.tweets;else throw new Z("Thread file must be an array of tweets or { tweets: [] }",Q.VALIDATION)}else{let Y=K($,"text","content");if(!Y)throw new Z("Provide --file thread.json (array of {content}) or informal --text with || separators is not enough alone for threads - use --file",Q.VALIDATION);w=Y.split("||").map((q)=>({content:q.trim()})).filter((q)=>q.content)}if(w.length<2)throw new Z("Threads require at least 2 tweets",Q.VALIDATION);if(u==="schedule-thread"){let Y=S({at:K($,"at"),in:K($,"in")}),q=await X.post("/api/posts/schedule-thread",{socialAccountId:O,tweets:w,scheduledAt:Y,metadata:_($)??{}},{workspace:!0});V(q,U);return}let J=await X.post("/api/posts/publish-thread-now",{socialAccountId:O,tweets:w,metadata:_($)??{}},{workspace:!0});V(J,U);return}default:throw new Z(`Unknown posts command "${u??""}". Try: list, get, schedule, publish, reschedule, cancel, delete, x-cap, schedule-thread, publish-thread`,Q.USAGE)}}async function kU(U,u,$,z){F(U),T(U);let X=k(U);switch(u){case"list":case"ls":{let D=await X.get("/api/drafts",{workspace:!0,query:{limit:d($,"limit")??50,offset:d($,"offset")??0}});V(D,{...U,emptyMessage:'No drafts yet. Create one with dun drafts create --text "..."'});return}case"get":{let D=z[0];if(!D)throw new Z("Usage: dun drafts get <id>",Q.USAGE);let O=await X.get(`/api/drafts/${D}`,{workspace:!0});V(O,U);return}case"create":{let D=K($,"text","content","c","t");if(!D)throw new Z("Missing --text",Q.VALIDATION);let O=await X.post("/api/drafts",{content:D,name:K($,"name","n"),socialAccountIds:L($,"accounts","account","a")??[],selectedPlatforms:L($,"platforms","platform","p")??[],mediaUrls:L($,"media")??[]},{workspace:!0});V(O,U);return}case"update":{let D=z[0];if(!D)throw new Z("Usage: dun drafts update <id> --text ...",Q.USAGE);let O={id:D},w=K($,"text","content","c","t");if(w)O.content=w;let J=K($,"name","n");if(J!==void 0)O.name=J;let Y=L($,"accounts","account","a");if(Y)O.socialAccountIds=Y;let q=L($,"platforms","platform","p");if(q)O.selectedPlatforms=q;let y=L($,"media");if(y)O.mediaUrls=y;let W=await X.patch(`/api/drafts/${D}`,O,{workspace:!0});V(W,U);return}case"delete":case"rm":{let D=z[0];if(!D)throw new Z("Usage: dun drafts delete <id> [--yes]",Q.USAGE);if(!U.yes&&!G($,"yes","y"))throw new Z("Refusing to delete without --yes",Q.VALIDATION);let O=await X.delete(`/api/drafts/${D}`,void 0,{workspace:!0});V(O??{id:D,deleted:!0},U);return}default:throw new Z(`Unknown drafts command "${u??""}". Try: list, get, create, update, delete`,Q.USAGE)}}import{readFileSync as z$,existsSync as Q$,statSync as X$}from"node:fs";import{basename as Z$}from"node:path";function J$(U){let u=U.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"}[u]??"application/octet-stream"}async function FU(U,u,$,z){F(U),T(U);let X=k(U);switch(u){case"list":case"ls":{let D=await X.get("/api/media",{workspace:!0,query:{limit:d($,"limit")??50,offset:d($,"offset")??0}});V(D,{...U,emptyMessage:"No media yet. Upload with dun media upload <file>."});return}case"get":{let D=z[0];if(!D)throw new Z("Usage: dun media get <id>",Q.USAGE);let O=await X.get(`/api/media/${D}`,{workspace:!0});V(O,U);return}case"upload":{let D=z[0]||K($,"file","f");if(!D)throw new Z('Usage: dun media upload <file> [--alt "..."]',Q.USAGE);if(!Q$(D))throw new Z(`File not found: ${D}`,Q.VALIDATION);let O=Z$(D),w=X$(D).size,J=K($,"mime","type","content-type")||J$(O),Y=K($,"alt","alt-text"),q=await X.post("/api/media/upload-url",{filename:O,mimeType:J,fileSize:w},{workspace:!0});m(U.debug,`PUT ${q.uploadUrl}`,{storageKey:q.storageKey,fileSize:w});let y=z$(D),W;try{W=await fetch(q.uploadUrl,{method:"PUT",headers:{"Content-Type":J,"Content-Length":String(w)},body:y})}catch(b){throw new Z(`Upload PUT failed: ${b.message}`,Q.NETWORK)}if(!W.ok){let b=await W.text().catch(()=>"");throw new Z(`Upload PUT failed with HTTP ${W.status}${b?`: ${b.slice(0,200)}`:""}`,Q.NETWORK)}let N=await X.post("/api/media/complete",{storageKey:q.storageKey,originalFilename:O,mimeType:J,fileSize:w,altText:Y},{workspace:!0});V(N,U);return}case"delete":case"rm":{let D=z[0];if(!D)throw new Z("Usage: dun media delete <id> [--yes]",Q.USAGE);if(!U.yes&&!G($,"yes","y"))throw new Z("Refusing to delete without --yes",Q.VALIDATION);let O=await X.delete(`/api/media/${D}`,void 0,{workspace:!0});V(O??{id:D,deleted:!0},U);return}default:throw new Z(`Unknown media command "${u??""}". Try: list, get, upload, delete`,Q.USAGE)}}async function RU(U,u,$,z){F(U),T(U);let X=k(U),D=u[0];if(D==="collections"||D==="collection"){let O=u[1]||"list";switch(O){case"list":case"ls":{let w=await X.get("/api/memory/collections",{workspace:!0});V(w,{...U,emptyMessage:'No memory collections yet. Create one with dun memory collections create --name "..."'});return}case"create":{let w=K($,"name","n");if(!w)throw new Z("Missing --name",Q.VALIDATION);let J=await X.post("/api/memory/collections",{name:w,color:K($,"color")??"blue",isPrivate:G($,"private")},{workspace:!0});V(J,U);return}case"get":{let w=u[2]||z[0];if(!w)throw new Z("Usage: dun memory collections get <id>",Q.USAGE);let J=await X.get(`/api/memory/collections/${w}`,{workspace:!0});V(J,U);return}default:throw new Z(`Unknown memory collections command "${O}". Try: list, create, get`,Q.USAGE)}}switch(D){case"list":case"ls":{let O=K($,"collection","collection-id","c");if(!O)throw new Z("Missing --collection <id>",Q.VALIDATION);let w=await X.get("/api/memory",{workspace:!0,query:{collectionId:O,limit:d($,"limit")??100}});V(w,{...U,emptyMessage:"No memories in this collection."});return}case"save":case"add":{let O=K($,"collection","collection-id","c"),w=K($,"text","t");if(!O)throw new Z("Missing --collection <id>",Q.VALIDATION);if(!w)throw new Z("Missing --text",Q.VALIDATION);let J=await X.post("/api/memory",{collectionId:O,text:w},{workspace:!0});V(J,U);return}case"search":{let O=K($,"prompt","q","query","text"),w=L($,"collections","collection","collection-ids","c")??[];if(!O)throw new Z("Missing --prompt",Q.VALIDATION);if(w.length===0)throw new Z("Missing --collections <id,id>",Q.VALIDATION);let J=await X.post("/api/memory/search",{prompt:O,collectionIds:w,topK:d($,"top-k","topk","k")??5},{workspace:!0});V(J,U);return}case"delete":case"rm":{let O=u[1]||z[0],w=K($,"collection","collection-id","c");if(!O)throw new Z("Usage: dun memory delete <id> --collection <id>",Q.USAGE);if(!w)throw new Z("Missing --collection <id>",Q.VALIDATION);if(!U.yes&&!G($,"yes","y"))throw new Z("Refusing to delete without --yes",Q.VALIDATION);let J=await X.delete(`/api/memory/${O}`,{collectionId:w,memoryId:O},{workspace:!0});V(J??{id:O,deleted:!0},U);return}default:throw new Z(`Unknown memory command "${D??""}". Try: collections, list, save, search, delete`,Q.USAGE)}}import{readFileSync as V$}from"node:fs";async function TU(U,u,$,z){F(U);let X=T(U),D=k(U),O=`/api/workspaces/${X}/ai-voice-settings`;switch(u){case"get":case"show":case void 0:{let w=await D.get(O,{workspace:!0});V(w??{personalization:null,hint:'No personalization set. Use dun personalization set --description "..."'},U);return}case"set":case"update":{let w=G($,"clear"),J=K($,"description","d"),Y=K($,"file","f");if([w,Boolean(J),Boolean(Y)].filter(Boolean).length!==1)throw new Z("Use exactly one of --description, --file, or --clear",Q.VALIDATION);let y;if(w)y=null;else if(Y){let N;try{N=V$(Y,"utf8").trim()}catch(b){throw new Z(`Could not read file: ${b.message}`,Q.VALIDATION)}if(!N)throw new Z("File is empty",Q.VALIDATION);y={description:N}}else{let N=J.trim();if(!N)throw new Z("--description cannot be empty",Q.VALIDATION);y={description:N}}let W=await D.patch(O,y,{workspace:!0});V(W,U);return}default:throw new Z(`Unknown personalization command "${u}". Try: get, set`,Q.USAGE)}}import{existsSync as Y$}from"node:fs";import{mkdir as BU,writeFile as PU,readFile as dU}from"node:fs/promises";import{dirname as l,join as R}from"node:path";import{homedir as a}from"node:os";import{fileURLToPath as q$}from"node:url";var p=[{id:"claude",label:"Claude Code",aliases:["claude-code"],dest:(U)=>R(U,".claude","skills","dunsocial","SKILL.md")},{id:"codex",label:"Codex",dest:(U)=>R(U,".codex","skills","dunsocial","SKILL.md"),afterInstall:async(U)=>{return{agentsPath:await G$(U)}}},{id:"cursor",label:"Cursor",dest:(U)=>R(U,".cursor","skills","dunsocial","SKILL.md")},{id:"pi",label:"Pi",dest:(U)=>R(U,".pi","agent","skills","dunsocial","SKILL.md")},{id:"opencode",label:"OpenCode",dest:(U)=>R(U,".config","opencode","skills","dunsocial","SKILL.md")},{id:"agents",label:"Universal (.agents)",aliases:["universal"],dest:(U)=>R(U,".agents","skills","dunsocial","SKILL.md")},{id:"copilot",label:"GitHub Copilot",aliases:["github-copilot","github"],dest:(U)=>R(U,".copilot","skills","dunsocial","SKILL.md")},{id:"windsurf",label:"Windsurf",dest:(U)=>R(U,".codeium","windsurf","skills","dunsocial","SKILL.md")},{id:"goose",label:"Goose",dest:(U)=>R(U,".config","goose","skills","dunsocial","SKILL.md")},{id:"gemini",label:"Gemini CLI",aliases:["gemini-cli"],dest:(U)=>R(U,".gemini","skills","dunsocial","SKILL.md")},{id:"amp",label:"Amp",dest:(U)=>R(U,".config","agents","skills","dunsocial","SKILL.md")},{id:"grok",label:"Grok Build",dest:(U)=>R(U,".grok","skills","dunsocial","SKILL.md")},{id:"continue",label:"Continue",dest:(U)=>R(U,".continue","skills","dunsocial","SKILL.md")},{id:"antigravity",label:"Antigravity",dest:(U)=>R(U,".gemini","antigravity","skills","dunsocial","SKILL.md")}],r=new Map;for(let U of p){r.set(U.id,U);for(let u of U.aliases??[])r.set(u,U)}var HU=`Usage: dun skill install <${p.map((U)=>U.id).join("|")}|all>`;function s(){let U=l(q$(import.meta.url)),u=R("skills","dunsocial","SKILL.md"),$=[R(U,"..",u),R(U,"..","..",u)];return $.find(Y$)??$[0]}function K$(){let U=a();return p.map((u)=>({id:u.id,label:u.label,path:u.dest(U),aliases:u.aliases??[]}))}async function y$(){try{return await dU(s(),"utf8")}catch{throw new Z(`Bundled skill not found at ${s()}. Reinstall dunsocial.`,Q.USAGE)}}async function W$(U,u){await BU(l(U),{recursive:!0}),await PU(U,u,"utf8")}async function G$(U){let u=R(a(),".codex","AGENTS.md");await BU(l(u),{recursive:!0});let $="";try{$=await dU(u,"utf8")}catch{$=""}let z=["<!-- BEGIN DUNSOCIAL CLI SKILL -->","## DunSocial CLI","","Use the `dun` CLI for DunSocial scheduling from the shell. Prefer `--json` output.",`Skill details: ${U}`,"","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(`
170
- `),X="<!-- BEGIN DUNSOCIAL CLI SKILL -->",D="<!-- END DUNSOCIAL CLI SKILL -->",O,w=$.indexOf(X),J=$.indexOf(D);if(w!==-1&&J!==-1&&J>w)O=$.slice(0,w)+z+$.slice(J+D.length);else if($.trim())O=`${$.trimEnd()}
171
-
172
- ${z}
173
- `;else O=`${z}
174
- `;return await PU(u,O,"utf8"),u}async function NU(U,u){let $=U.dest(a());await W$($,u);let z=U.afterInstall?await U.afterInstall($):{};return{target:U.id,path:$,message:`Installed DunSocial skill for ${U.label}. Restart or open a new session if needed.`,...z}}async function LU(U,u,$,z){switch(u){case"path":{V({path:s()},U);return}case"list":{V({targets:K$(),hint:"Install with: dun skill install <target|all>"},U);return}case"install":{let X=(z[0]||"").toLowerCase();if(!X)throw new Z(HU,Q.USAGE);let D=await y$();if(X==="all"){let J=[];for(let Y of p){let q=await NU(Y,D);J.push({target:q.target,path:q.path})}V({target:"all",count:J.length,installed:J,message:`Installed DunSocial skill for ${J.length} agents.`},U);return}let O=r.get(X);if(!O)throw new Z(`Unknown agent "${X}". ${HU}
175
- Try: dun skill list`,Q.USAGE);let w=await NU(O,D);V(w,U);return}default:throw new Z(`Unknown skill command "${u??""}". Try: install, list, path`,Q.USAGE)}}import{spawn as k$}from"node:child_process";import{realpathSync as F$}from"node:fs";var H="dunsocial",R$=`https://registry.npmjs.org/${H}/latest`;function T$(U=process.argv[1]??""){let u=U.replaceAll("\\","/");try{u=F$(U).replaceAll("\\","/")}catch{}if(u.includes("/.npm/_npx")||u.includes("/npm/_npx")||u.includes("/_npx/"))return{packageManager:"npx",manual:`npm install -g ${H}@latest`};if(u.includes("/.pnpm/")||u.includes("/pnpm/"))return{packageManager:"pnpm",command:"pnpm",args:["add","-g",`${H}@latest`],manual:`pnpm add -g ${H}@latest`};if(u.includes("/.yarn/")||u.includes("/yarn/global"))return{packageManager:"yarn",command:"yarn",args:["global","add",`${H}@latest`],manual:`yarn global add ${H}@latest`};if(u.includes("/.bun/")||u.includes("/bun/install/global"))return{packageManager:"bun",command:"bun",args:["add","-g",`${H}@latest`],manual:`bun add -g ${H}@latest`};if(u.includes(`/node_modules/${H}/`))return{packageManager:"npm",command:"npm",args:["install","-g",`${H}@latest`],manual:`npm install -g ${H}@latest`};return{packageManager:"unknown",manual:`npm install -g ${H}@latest`}}function H$(U,u){let $=U.replace(/^v/,"").split(".").map((D)=>Number.parseInt(D,10)||0),z=u.replace(/^v/,"").split(".").map((D)=>Number.parseInt(D,10)||0),X=Math.max($.length,z.length);for(let D=0;D<X;D++){let O=$[D]??0,w=z[D]??0;if(O>w)return 1;if(O<w)return-1}return 0}async function N$(U=fetch){let u=await U(R$,{headers:{Accept:"application/json"}});if(!u.ok)throw new Z(`Could not check npm for updates (HTTP ${u.status}). Try: npm install -g ${H}@latest`,Q.NETWORK);let $=await u.json();if(!$.version)throw new Z("npm registry response missing version",Q.NETWORK);return $.version}function B$(U,u){return new Promise(($,z)=>{let X=k$(U,u,{stdio:"inherit",env:process.env,windowsHide:!0,shell:process.platform==="win32"});X.on("error",(D)=>{z(new Z(`Failed to run ${U}: ${D.message}. Try manually: ${U} ${u.join(" ")}`,Q.NETWORK))}),X.on("close",(D)=>$(D??1))})}async function AU(U,u,$){let z=G($,"check"),X=G($,"force"),D=B,O=T$(),w;try{w=await N$()}catch(y){if(y instanceof Z)throw y;throw new Z(`Could not check npm for updates: ${y.message}`,Q.NETWORK)}let J=H$(w,D)>0,Y={current:D,latest:w,upToDate:!J,packageManager:O.packageManager,command:O.manual};if(!J&&!X){V(U.json?Y:`Already on the latest version (${D}).`,U);return}if(z){V(U.json?{...Y,updateAvailable:J}:`Update available: ${D} → ${w}
223
+ `)}else process.stderr.write(JSON.stringify({ok:!0,phase:"waiting",userCode:D.userCode,verificationUrlComplete:D.verificationUrlComplete})+`
224
+ `);if(y&&process.stdout.isTTY)try{await yd(D.verificationUrlComplete)}catch{}let m=Math.max(3,D.interval||5)*1000,U=Date.now()+(D.expiresIn||900)*1000;while(Date.now()<U){await Od(m);let $=await d.post("/api/cli/auth/poll",{deviceCode:D.deviceCode},{auth:!1});if($.status==="pending")continue;if($.status==="expired")throw new z("Login expired. Run dun auth login again.",O.AUTH);if($.status==="denied")throw new z("Login was denied in the browser.",O.AUTH);if($.status==="approved"){let q=$.token,X={...u,token:q},Z=b(X),Q=await Z.get("/api/user/profile"),J={...u.config,apiUrl:u.apiUrl,token:q,defaultWorkspaceId:$.workspaceId||u.config.defaultWorkspaceId,defaultWorkspaceSlug:u.config.defaultWorkspaceSlug,defaultWorkspaceName:u.config.defaultWorkspaceName};if($.workspaceId)try{let A=(await Z.get("/api/workspaces")).find((vu)=>vu.id===$.workspaceId);if(A)J.defaultWorkspaceId=A.id,J.defaultWorkspaceSlug=A.slug,J.defaultWorkspaceName=A.name}catch{}await j(J);let G=Boolean(J.defaultWorkspaceId);if(!u.json&&H(process.stdout)){k(v(G),u);return}k({user:{id:Q.id,name:Q.name,email:Q.email},workspaceId:J.defaultWorkspaceId??null,configPath:_(),method:"device",message:v(G)},u);return}}throw new z("Login timed out. Run dun auth login again.",O.AUTH)}async function $d(u){if(await wu(),!u.json&&H(process.stdout)){k("Logged out. Config cleared.",u);return}k({message:"Logged out. Config cleared.",configPath:_()},u)}async function Ud(u){Y(u);let d=await b(u).get("/api/user/profile");k({id:d.id,name:d.name,email:d.email,image:d.image??null,apiUrl:u.apiUrl,workspaceId:u.workspaceId??null},u)}async function Dd(u){let w=Boolean(u.token),d=null,D=null;if(w)try{d=await b(u).get("/api/user/profile")}catch(y){D=y instanceof Error?y.message:String(y)}k({authenticated:Boolean(d),tokenPresent:w,apiUrl:u.apiUrl,workspaceId:u.workspaceId??null,workspaceSlug:u.config.defaultWorkspaceSlug??null,workspaceName:u.config.defaultWorkspaceName??null,configPath:_(),user:d?{id:d.id,name:d.name,email:d.email}:null,error:D},u)}function Od(u){return new Promise((w)=>setTimeout(w,u))}async function yd(u){let w=process.platform,d=w==="darwin"?["open",u]:w==="win32"?["cmd","/c","start","",u]:["xdg-open",u];await new Promise((D,y)=>{let m=ud(d[0],d.slice(1),{stdio:"ignore",windowsHide:!0});m.on("error",y),m.on("close",()=>D())})}async function Vu(u,w,d,D){Y(u);let y=b(u);switch(w){case"list":case"ls":{let m=await y.get("/api/workspaces");k(m.map((U)=>({id:U.id,name:U.name,slug:U.slug,role:U.role,timezone:U.timezone,plan:U.subscription?.planName??null,status:U.subscription?.status??null})),{...u,emptyMessage:"No workspaces yet."});return}case"use":{let m=D[0];if(!m)throw new z("Usage: dun workspace use <id|slug>",O.USAGE);let $=(await y.get("/api/workspaces")).find((q)=>q.id===m||q.slug===m);if(!$)throw new z(`Workspace not found: ${m}. Run dun workspace list`,O.NOT_FOUND);await j({...u.config,apiUrl:u.apiUrl,token:u.token,defaultWorkspaceId:$.id,defaultWorkspaceSlug:$.slug,defaultWorkspaceName:$.name}),k({id:$.id,name:$.name,slug:$.slug,role:$.role,message:`Default workspace set to ${$.name}`},u);return}case"current":{if(!u.workspaceId&&!u.config.defaultWorkspaceId)throw new z("No workspace selected. Run dun workspace use <id|slug>",O.VALIDATION);let m=u.workspaceId||u.config.defaultWorkspaceId;try{let $=(await y.get("/api/workspaces")).find((q)=>q.id===m||q.slug===m);if($){k({id:$.id,name:$.name,slug:$.slug,role:$.role,timezone:$.timezone},u);return}}catch{}k({id:m,name:u.config.defaultWorkspaceName??null,slug:u.config.defaultWorkspaceSlug??null},u);return}default:throw new z(`Unknown workspace command "${w??""}". Try: list, use, current`,O.USAGE)}}async function Qu(u,w,d,D){Y(u),F(u);let y=b(u);switch(w){case void 0:case"list":case"ls":{let m=await y.get("/api/social-accounts",{workspace:!0}),U=V(d,"platform","p")?.toLowerCase(),$=U?m.filter((q)=>{let X=(q.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"]}[U]??[U]).some((J)=>X.includes(J))}):m;k($.map((q)=>({id:q.id,providerName:q.providerName,username:q.username??null,displayName:q.displayName??null,isConnected:q.isConnected,expiresAt:q.expiresAt??null})),{...u,emptyMessage:U?`No ${U} accounts connected. Connect one in the DunSocial dashboard.`:"No accounts connected. Connect one in the DunSocial dashboard."});return}default:throw new z(`Unknown accounts command "${w}". Try: list (or: dun accounts reddit flairs <subreddit>)`,O.USAGE)}}import{readFileSync as Xd}from"node:fs";var zd=/^(\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 r(u,w=new Date){let d=u.trim();if(!d)throw new z("Empty time value",O.VALIDATION);let D=d.match(zd);if(D){let U=Number(D[1]),$=D[2].toLowerCase(),q=qd($)*U;return new Date(w.getTime()+q)}let y=d.match(/^in\s+(.+)$/i);if(y)return r(y[1],w);let m=new Date(d);if(Number.isNaN(m.getTime()))throw new z(`Invalid time "${u}". Use ISO-8601 (2026-06-01T09:00:00Z) or relative (2h, 30m, 1d).`,O.VALIDATION);return m}function qd(u){switch(u){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 z(`Unknown time unit: ${u}`,O.VALIDATION)}}function e(u){if(u.at&&u.in)throw new z("Pass only one of --at or --in",O.VALIDATION);if(u.at)return r(u.at).toISOString();if(u.in)return r(u.in).toISOString();throw new z("Schedule time required. Pass --at <iso> or --in <duration> (e.g. 2h).",O.VALIDATION)}function Ru(u){if(!u)return[];return u.split(",").map((w)=>w.trim()).filter(Boolean)}function Ju(u,w){let d=V(u,"text","content","c","t");if(d)return d;let D=w?.reddit;if(D&&typeof D==="object"&&!Array.isArray(D)&&typeof D.title==="string"&&D.title.trim())return D.title.trim();throw new z("Missing --text",O.VALIDATION)}function S(u){let w=I(u,"accounts","account","a")??Ru(V(u,"accounts","account","a"));if(!w||w.length===0)throw new z("Missing --accounts <id,id>",O.VALIDATION);return w}function h(u){return I(u,"media","media-urls")??Ru(V(u,"media","media-urls"))}function N(u){let w=V(u,"meta","metadata","meta-json");if(!w)return;try{let d=JSON.parse(w);if(!d||typeof d!=="object"||Array.isArray(d))throw Error("metadata must be a JSON object");return d}catch(d){throw new z(`Invalid --meta JSON: ${d.message}`,O.VALIDATION)}}var Zd=new Set(["published","failed","cancelled"]);async function kd(u){await new Promise((w)=>setTimeout(w,u))}async function Vd(u,w,d,D){let y=Date.now()+D*1000,m=new Map,U=1500;while(m.size<d.length){if(Date.now()>y){let X=d.filter((Z)=>!m.has(Z.id)).map((Z)=>Z.id);throw new z(`Timed out waiting for publish after ${D}s. Still pending: ${X.join(", ")}. Poll with: dun posts get <id> --json`,O.NETWORK,{pending:X})}for(let X of d){if(m.has(X.id))continue;let Z=await u.get(`/api/posts/${X.id}`,{workspace:!0});if(P(w.debug,`wait status ${X.id}=${Z.status}`),Zd.has(Z.status))m.set(X.id,Z)}if(m.size>=d.length)break;await kd(U),U=Math.min(U*1.4,8000)}let $=d.map((X)=>m.get(X.id)),q=$.filter((X)=>X.status==="failed");if(q.length>0){let X=q.map((Z)=>`${Z.id}: ${Z.error||"publish failed"}`).join("; ");throw new z(`Publish failed — ${X}`,O.VALIDATION,{posts:$})}return $}async function Qd(u,w){let d=[],D=w.meta?.reddit??null;if(!D||typeof D!=="object")throw new z("Reddit accounts require --meta with a reddit object, e.g. "+`--meta '{"reddit":{"subreddit":"…","kind":"self","title":"…","flairId":"…"}}'`,O.VALIDATION);let y=(D.subreddit??"").trim().replace(/^\/?r\//i,""),m=(D.title??"").trim(),U=(D.kind??"").trim(),$=(D.flairId??"").trim(),q=(D.flairText??"").trim();if(!y)throw new z("metadata.reddit.subreddit is required",O.VALIDATION);if(!m)throw new z("metadata.reddit.title is required",O.VALIDATION);if(m.length>300)throw new z(`Reddit title exceeds 300 characters: ${m.length}/300`,O.VALIDATION);if(!["self","link","image","video"].includes(U))throw new z("metadata.reddit.kind must be one of: self, link, image, video",O.VALIDATION);if(d.push(`kind=${U}`),d.push(`titleLength=${m.length}`),q&&!$)throw new z(`Can't set flairText without flairId for r/${y}. Run: dun reddit flairs ${y} --json`,O.VALIDATION);if(U==="link"){if(!D.url?.trim())throw new z("Reddit link posts require metadata.reddit.url",O.VALIDATION);try{new URL(D.url)}catch{throw new z("metadata.reddit.url is invalid",O.VALIDATION)}if(w.media.length>0)throw new z("Reddit link posts do not accept --media",O.VALIDATION)}else if(U==="image"||U==="video"){if(w.media.length!==1)throw new z(`Reddit ${U} posts require exactly one --media asset id`,O.VALIDATION)}else if(w.media.length>0)throw new z("Reddit self posts do not accept --media. Use kind image or video.",O.VALIDATION);let X=await u.get("/api/social-accounts/reddit/post-requirements",{workspace:!0,query:{subreddit:y}});if(d.push("fetched post-requirements"),X?.is_flair_required&&!$)throw new z(`r/${y} requires post flair. Discover ids with: dun reddit flairs ${y} --json`,O.VALIDATION);let Z=X?.title_text_max_length??300;if(m.length>Z)throw new z(`Reddit title too long for r/${y}: ${m.length}/${Z}`,O.VALIDATION);if(X?.title_text_min_length&&m.length<X.title_text_min_length)throw new z(`Reddit title must be at least ${X.title_text_min_length} characters`,O.VALIDATION);if($){let Q=await u.get("/api/social-accounts/reddit/flair-options",{workspace:!0,query:{subreddit:y}});d.push(`fetched ${Q.choices?.length??0} flairs`);let J=(Q.choices??[]).find((G)=>G.id===$);if(!J)throw new z(`Unknown flairId "${$}" for r/${y}. Run: dun reddit flairs ${y} --json`,O.VALIDATION);if(J.textEditable===!1&&q&&q!==J.text)throw new z(`Customization not allowed for this flair template. Use flairId "${J.id}" alone`+(J.text?` (template text: "${J.text}")`:""),O.VALIDATION);d.push(`flairId=${J.id}`)}return{ok:!0,checks:d,reddit:{subreddit:y,kind:U,title:m,flairId:$||null,accounts:w.accounts,media:w.media,textPreview:w.text?.slice(0,120)??null,requirements:{is_flair_required:X?.is_flair_required??!1,title_text_max_length:Z}}}}async function Gu(u,w,d,D){Y(u),F(u);let y=b(u);switch(w){case"list":case"ls":{let m=await y.get("/api/posts",{workspace:!0,query:{status:V(d,"status","s"),socialAccountId:V(d,"account","social-account"),groupId:V(d,"group"),limit:B(d,"limit")??50,offset:B(d,"offset")??0}});k(m,{...u,emptyMessage:"No posts yet. Schedule one with dun posts schedule."});return}case"get":{let m=D[0];if(!m)throw new z("Usage: dun posts get <id>",O.USAGE);let U=await y.get(`/api/posts/${m}`,{workspace:!0});k(U,u);return}case"validate":{let m=S(d),U=h(d),$=N(d)??{},q=V(d,"text","content","c","t"),X=Boolean($.reddit),Z=X;if(!X){let J=await y.get("/api/social-accounts",{workspace:!0});Z=m.some((G)=>{return(J.find((A)=>A.id===G)?.providerName||"").toLowerCase().includes("reddit")})}if(!Z){k({ok:!0,checks:["no reddit-specific rules for selected accounts"],accounts:m,media:U},u);return}let Q=await Qd(y,{accounts:m,text:q,media:U,meta:$});k(Q,u);return}case"schedule":{let m=N(d)??{},U=Ju(d,m),$=S(d),q=e({at:V(d,"at"),in:V(d,"in")}),X=await y.post("/api/posts/schedule",{content:U,socialAccountIds:$,scheduledAt:q,mediaUrls:h(d),metadata:m},{workspace:!0});k(X,u);return}case"publish":case"publish-now":{let m=N(d)??{},U=Ju(d,m),$=S(d),q=await y.post("/api/posts/publish-now",{content:U,socialAccountIds:$,mediaUrls:h(d),metadata:m,naturalPosting:R(d,"natural","natural-posting")},{workspace:!0});if(!R(d,"wait")){k(q,u);return}let Z=Array.isArray(q)?q:[];if(Z.length===0||!Z.every((G)=>typeof G?.id==="string"))throw new z("publish --wait expected an array of created posts with ids",O.NETWORK,{data:q});let Q=B(d,"wait-timeout","timeout")??120,J=await Vd(y,u,Z,Q);k({queued:Z,posts:J,status:J.every((G)=>G.status==="published")?"published":J[0]?.status},u);return}case"reschedule":{let m=D[0];if(!m)throw new z("Usage: dun posts reschedule <id> --at|--in",O.USAGE);let $={scheduledAt:e({at:V(d,"at"),in:V(d,"in")})},q=V(d,"text","content","c","t");if(q)$.content=q;let X=h(d);if(X.length)$.mediaUrls=X;let Z=N(d);if(Z)$.metadata=Z;if(!$.content){let J=await y.get(`/api/posts/${m}`,{workspace:!0});if($.content=J.content,!$.metadata&&J.metadata)$.metadata=J.metadata;if(!$.mediaUrls&&J.mediaUrls)$.mediaUrls=J.mediaUrls}let Q=await y.post(`/api/posts/${m}/reschedule`,$,{workspace:!0});k(Q,u);return}case"cancel":{let m=D[0];if(!m)throw new z("Usage: dun posts cancel <id>",O.USAGE);let U=await y.post(`/api/posts/${m}/cancel`,void 0,{workspace:!0});k(U??{id:m,status:"cancelled"},u);return}case"delete":case"rm":{let m=D[0];if(!m)throw new z("Usage: dun posts delete <id> [--yes]",O.USAGE);if(!u.yes&&!R(d,"yes","y"))throw new z("Refusing to delete without --yes (or CI/yes mode)",O.VALIDATION);let U=await y.delete(`/api/posts/${m}`,void 0,{workspace:!0});k(U??{id:m,deleted:!0},u);return}case"x-cap":case"xcap":{let m=await y.get("/api/posts/x-cap-usage",{workspace:!0});k(m,u);return}case"schedule-thread":case"publish-thread":case"publish-thread-now":{let m=V(d,"file","f"),U=V(d,"account","accounts","a")??S(d)[0];if(!U)throw new z("Missing --account <xAccountId>",O.VALIDATION);let $;if(m){let X;try{X=Xd(m,"utf8")}catch(Q){throw new z(`Could not read file: ${Q.message}`,O.VALIDATION)}let Z=JSON.parse(X);if(Array.isArray(Z))$=Z;else if(Z&&typeof Z==="object"&&Array.isArray(Z.tweets))$=Z.tweets;else throw new z("Thread file must be an array of tweets or { tweets: [] }",O.VALIDATION)}else{let X=V(d,"text","content");if(!X)throw new z("Provide --file thread.json (array of {content}) or informal --text with || separators is not enough alone for threads - use --file",O.VALIDATION);$=X.split("||").map((Z)=>({content:Z.trim()})).filter((Z)=>Z.content)}if($.length<2)throw new z("Threads require at least 2 tweets",O.VALIDATION);if(w==="schedule-thread"){let X=e({at:V(d,"at"),in:V(d,"in")}),Z=await y.post("/api/posts/schedule-thread",{socialAccountId:U,tweets:$,scheduledAt:X,metadata:N(d)??{}},{workspace:!0});k(Z,u);return}let q=await y.post("/api/posts/publish-thread-now",{socialAccountId:U,tweets:$,metadata:N(d)??{}},{workspace:!0});k(q,u);return}default:throw new z(`Unknown posts command "${w??""}". Try: list, get, validate, schedule, publish, reschedule, cancel, delete, x-cap, schedule-thread, publish-thread`,O.USAGE)}}async function bu(u,w,d,D){Y(u),F(u);let y=b(u);switch(w){case"list":case"ls":{let m=await y.get("/api/drafts",{workspace:!0,query:{limit:B(d,"limit")??50,offset:B(d,"offset")??0}});k(m,{...u,emptyMessage:'No drafts yet. Create one with dun drafts create --text "..."'});return}case"get":{let m=D[0];if(!m)throw new z("Usage: dun drafts get <id>",O.USAGE);let U=await y.get(`/api/drafts/${m}`,{workspace:!0});k(U,u);return}case"create":{let m=V(d,"text","content","c","t");if(!m)throw new z("Missing --text",O.VALIDATION);let U=await y.post("/api/drafts",{content:m,name:V(d,"name","n"),socialAccountIds:I(d,"accounts","account","a")??[],selectedPlatforms:I(d,"platforms","platform","p")??[],mediaUrls:I(d,"media")??[]},{workspace:!0});k(U,u);return}case"update":{let m=D[0];if(!m)throw new z("Usage: dun drafts update <id> --text ...",O.USAGE);let U={id:m},$=V(d,"text","content","c","t");if($)U.content=$;let q=V(d,"name","n");if(q!==void 0)U.name=q;let X=I(d,"accounts","account","a");if(X)U.socialAccountIds=X;let Z=I(d,"platforms","platform","p");if(Z)U.selectedPlatforms=Z;let Q=I(d,"media");if(Q)U.mediaUrls=Q;let J=await y.patch(`/api/drafts/${m}`,U,{workspace:!0});k(J,u);return}case"delete":case"rm":{let m=D[0];if(!m)throw new z("Usage: dun drafts delete <id> [--yes]",O.USAGE);if(!u.yes&&!R(d,"yes","y"))throw new z("Refusing to delete without --yes",O.VALIDATION);let U=await y.delete(`/api/drafts/${m}`,void 0,{workspace:!0});k(U??{id:m,deleted:!0},u);return}default:throw new z(`Unknown drafts command "${w??""}". Try: list, get, create, update, delete`,O.USAGE)}}import{readFileSync as Jd,existsSync as Rd,statSync as Gd}from"node:fs";import{basename as bd}from"node:path";var Yu=["image/jpeg","image/png","image/gif","image/webp"],nu=["video/mp4","video/quicktime","video/webm"];function Fu(u){if(u<1024)return`${u}B`;if(u<1048576)return`${(u/1024).toFixed(1)}KB`;return`${(u/1048576).toFixed(1)}MB`}function Wu(u){let{mimeType:w,fileSize:d}=u,D=Yu.includes(w),y=nu.includes(w);if(!D&&!y)return{ok:!1,message:`Unsupported MIME type "${w}". Allowed images: ${Yu.join(", ")}; videos: ${nu.join(", ")}`};if(y&&d>268435456)return{ok:!1,message:`Video files must be less than 256MB (got ${Fu(d)}). Compress first, e.g.:
225
+ ffmpeg -i input.mp4 -vf scale=-2:1080 -c:v libx264 -crf 23 -c:a aac output.mp4`};if(D&&d>8388608)return{ok:!1,message:`Image files must be less than 8MB (got ${Fu(d)}).`};return{ok:!0}}function Yd(u){let w=u.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"}[w]??"application/octet-stream"}async function Ku(u,w,d,D){Y(u),F(u);let y=b(u);switch(w){case"list":case"ls":{let m=await y.get("/api/media",{workspace:!0,query:{limit:B(d,"limit")??50,offset:B(d,"offset")??0}});k(m,{...u,emptyMessage:"No media yet. Upload with dun media upload <file>."});return}case"get":{let m=D[0];if(!m)throw new z("Usage: dun media get <id>",O.USAGE);let U=await y.get(`/api/media/${m}`,{workspace:!0});k(U,u);return}case"upload":{let m=D[0]||V(d,"file","f");if(!m)throw new z('Usage: dun media upload <file> [--alt "..."]',O.USAGE);if(!Rd(m))throw new z(`File not found: ${m}`,O.VALIDATION);let U=bd(m),$=Gd(m).size,q=V(d,"mime","type","content-type")||Yd(U),X=V(d,"alt","alt-text"),Z=Wu({mimeType:q,fileSize:$});if(!Z.ok)throw new z(Z.message,O.VALIDATION);let Q=await y.post("/api/media/upload-url",{filename:U,mimeType:q,fileSize:$},{workspace:!0});P(u.debug,`PUT ${Q.uploadUrl}`,{storageKey:Q.storageKey,fileSize:$});let J=Jd(m),G;try{G=await fetch(Q.uploadUrl,{method:"PUT",headers:{"Content-Type":q,"Content-Length":String($)},body:J})}catch(A){throw new z(`Upload PUT failed: ${A.message}`,O.NETWORK)}if(!G.ok){let A=await G.text().catch(()=>"");throw new z(`Upload PUT failed with HTTP ${G.status}${A?`: ${A.slice(0,200)}`:""}`,O.NETWORK)}let M=await y.post("/api/media/complete",{storageKey:Q.storageKey,originalFilename:U,mimeType:q,fileSize:$,altText:X},{workspace:!0});k(M,u);return}case"delete":case"rm":{let m=D[0];if(!m)throw new z("Usage: dun media delete <id> [--yes]",O.USAGE);if(!u.yes&&!R(d,"yes","y"))throw new z("Refusing to delete without --yes",O.VALIDATION);let U=await y.delete(`/api/media/${m}`,void 0,{workspace:!0});k(U??{id:m,deleted:!0},u);return}default:throw new z(`Unknown media command "${w??""}". Try: list, get, upload, delete`,O.USAGE)}}async function Tu(u,w,d,D){Y(u),F(u);let y=b(u),m=w[0];if(m==="collections"||m==="collection"){let U=w[1]||"list";switch(U){case"list":case"ls":{let $=await y.get("/api/memory/collections",{workspace:!0});k($,{...u,emptyMessage:'No memory collections yet. Create one with dun memory collections create --name "..."'});return}case"create":{let $=V(d,"name","n");if(!$)throw new z("Missing --name",O.VALIDATION);let q=await y.post("/api/memory/collections",{name:$,color:V(d,"color")??"blue",isPrivate:R(d,"private")},{workspace:!0});k(q,u);return}case"get":{let $=w[2]||D[0];if(!$)throw new z("Usage: dun memory collections get <id>",O.USAGE);let q=await y.get(`/api/memory/collections/${$}`,{workspace:!0});k(q,u);return}default:throw new z(`Unknown memory collections command "${U}". Try: list, create, get`,O.USAGE)}}switch(m){case"list":case"ls":{let U=V(d,"collection","collection-id","c");if(!U)throw new z("Missing --collection <id>",O.VALIDATION);let $=await y.get("/api/memory",{workspace:!0,query:{collectionId:U,limit:B(d,"limit")??100}});k($,{...u,emptyMessage:"No memories in this collection."});return}case"save":case"add":{let U=V(d,"collection","collection-id","c"),$=V(d,"text","t");if(!U)throw new z("Missing --collection <id>",O.VALIDATION);if(!$)throw new z("Missing --text",O.VALIDATION);let q=await y.post("/api/memory",{collectionId:U,text:$},{workspace:!0});k(q,u);return}case"search":{let U=V(d,"prompt","q","query","text"),$=I(d,"collections","collection","collection-ids","c")??[];if(!U)throw new z("Missing --prompt",O.VALIDATION);if($.length===0)throw new z("Missing --collections <id,id>",O.VALIDATION);let q=await y.post("/api/memory/search",{prompt:U,collectionIds:$,topK:B(d,"top-k","topk","k")??5},{workspace:!0});k(q,u);return}case"delete":case"rm":{let U=w[1]||D[0],$=V(d,"collection","collection-id","c");if(!U)throw new z("Usage: dun memory delete <id> --collection <id>",O.USAGE);if(!$)throw new z("Missing --collection <id>",O.VALIDATION);if(!u.yes&&!R(d,"yes","y"))throw new z("Refusing to delete without --yes",O.VALIDATION);let q=await y.delete(`/api/memory/${U}`,{collectionId:$,memoryId:U},{workspace:!0});k(q??{id:U,deleted:!0},u);return}default:throw new z(`Unknown memory command "${m??""}". Try: collections, list, save, search, delete`,O.USAGE)}}import{readFileSync as nd}from"node:fs";async function Bu(u,w,d,D){Y(u);let y=F(u),m=b(u),U=`/api/workspaces/${y}/ai-voice-settings`;switch(w){case"get":case"show":case void 0:{let $=await m.get(U,{workspace:!0});k($??{personalization:null,hint:'No personalization set. Use dun personalization set --description "..."'},u);return}case"set":case"update":{let $=R(d,"clear"),q=V(d,"description","d"),X=V(d,"file","f");if([$,Boolean(q),Boolean(X)].filter(Boolean).length!==1)throw new z("Use exactly one of --description, --file, or --clear",O.VALIDATION);let Q;if($)Q=null;else if(X){let G;try{G=nd(X,"utf8").trim()}catch(M){throw new z(`Could not read file: ${M.message}`,O.VALIDATION)}if(!G)throw new z("File is empty",O.VALIDATION);Q={description:G}}else{let G=q.trim();if(!G)throw new z("--description cannot be empty",O.VALIDATION);Q={description:G}}let J=await m.patch(U,Q,{workspace:!0});k(J,u);return}default:throw new z(`Unknown personalization command "${w}". Try: get, set`,O.USAGE)}}function Iu(u){return u.trim().replace(/^\/?r\//i,"").replace(/^@/,"")}async function c(u,w,d,D){Y(u),F(u);let y=b(u);switch(w){case"flairs":case"flair":case"flair-options":{let m=D[0]||V(d,"subreddit","sub","r");if(!m)throw new z(`Usage: dun reddit flairs <subreddit>
226
+ Example: dun reddit flairs tamilyapping --json`,O.USAGE);let U=Iu(m),q=((await y.get("/api/social-accounts/reddit/flair-options",{workspace:!0,query:{subreddit:U}})).choices??[]).map((X)=>({id:X.id,text:X.text,textEditable:X.textEditable!==!1}));k({subreddit:U,choices:q},{...u,emptyMessage:`No flairs returned for r/${U}. The subreddit may not use post flair.`});return}case"requirements":case"post-requirements":{let m=D[0]||V(d,"subreddit","sub","r");if(!m)throw new z("Usage: dun reddit requirements <subreddit>",O.USAGE);let U=Iu(m),$=await y.get("/api/social-accounts/reddit/post-requirements",{workspace:!0,query:{subreddit:U}});k({subreddit:U,requirements:$},u);return}default:throw new z(`Unknown reddit command "${w??""}". Try: flairs, requirements`,O.USAGE)}}import{existsSync as Fd}from"node:fs";import{mkdir as Pu,writeFile as Nu,readFile as Mu}from"node:fs/promises";import{dirname as l,join as n}from"node:path";import{homedir as t}from"node:os";import{fileURLToPath as Wd}from"node:url";var p=[{id:"claude",label:"Claude Code",aliases:["claude-code"],dest:(u)=>n(u,".claude","skills","dunsocial","SKILL.md")},{id:"codex",label:"Codex",dest:(u)=>n(u,".codex","skills","dunsocial","SKILL.md"),afterInstall:async(u)=>{return{agentsPath:await Id(u)}}},{id:"cursor",label:"Cursor",dest:(u)=>n(u,".cursor","skills","dunsocial","SKILL.md")},{id:"pi",label:"Pi",dest:(u)=>n(u,".pi","agent","skills","dunsocial","SKILL.md")},{id:"opencode",label:"OpenCode",dest:(u)=>n(u,".config","opencode","skills","dunsocial","SKILL.md")},{id:"agents",label:"Universal (.agents)",aliases:["universal"],dest:(u)=>n(u,".agents","skills","dunsocial","SKILL.md")},{id:"copilot",label:"GitHub Copilot",aliases:["github-copilot","github"],dest:(u)=>n(u,".copilot","skills","dunsocial","SKILL.md")},{id:"windsurf",label:"Windsurf",dest:(u)=>n(u,".codeium","windsurf","skills","dunsocial","SKILL.md")},{id:"goose",label:"Goose",dest:(u)=>n(u,".config","goose","skills","dunsocial","SKILL.md")},{id:"gemini",label:"Gemini CLI",aliases:["gemini-cli"],dest:(u)=>n(u,".gemini","skills","dunsocial","SKILL.md")},{id:"amp",label:"Amp",dest:(u)=>n(u,".config","agents","skills","dunsocial","SKILL.md")},{id:"grok",label:"Grok Build",dest:(u)=>n(u,".grok","skills","dunsocial","SKILL.md")},{id:"continue",label:"Continue",dest:(u)=>n(u,".continue","skills","dunsocial","SKILL.md")},{id:"antigravity",label:"Antigravity",dest:(u)=>n(u,".gemini","antigravity","skills","dunsocial","SKILL.md")}],x=new Map;for(let u of p){x.set(u.id,u);for(let w of u.aliases??[])x.set(w,u)}var Au=`Usage: dun skill install <${p.map((u)=>u.id).join("|")}|all>`;function a(){let u=l(Wd(import.meta.url)),w=n("skills","dunsocial","SKILL.md"),d=[n(u,"..",w),n(u,"..","..",w)];return d.find(Fd)??d[0]}function Kd(){let u=t();return p.map((w)=>({id:w.id,label:w.label,path:w.dest(u),aliases:w.aliases??[]}))}async function Td(){try{return await Mu(a(),"utf8")}catch{throw new z(`Bundled skill not found at ${a()}. Reinstall dunsocial.`,O.USAGE)}}async function Bd(u,w){await Pu(l(u),{recursive:!0}),await Nu(u,w,"utf8")}async function Id(u){let w=n(t(),".codex","AGENTS.md");await Pu(l(w),{recursive:!0});let d="";try{d=await Mu(w,"utf8")}catch{d=""}let D=["<!-- BEGIN DUNSOCIAL CLI SKILL -->","## DunSocial CLI","","Use the `dun` CLI for DunSocial scheduling from the shell. Prefer `--json` output.",`Skill details: ${u}`,"","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(`
227
+ `),y="<!-- BEGIN DUNSOCIAL CLI SKILL -->",m="<!-- END DUNSOCIAL CLI SKILL -->",U,$=d.indexOf(y),q=d.indexOf(m);if($!==-1&&q!==-1&&q>$)U=d.slice(0,$)+D+d.slice(q+m.length);else if(d.trim())U=`${d.trimEnd()}
228
+
229
+ ${D}
230
+ `;else U=`${D}
231
+ `;return await Nu(w,U,"utf8"),w}async function Hu(u,w){let d=u.dest(t());await Bd(d,w);let D=u.afterInstall?await u.afterInstall(d):{};return{target:u.id,path:d,message:`Installed DunSocial skill for ${u.label}. Restart or open a new session if needed.`,...D}}async function _u(u,w,d,D){switch(w){case"path":{k({path:a()},u);return}case"list":{k({targets:Kd(),hint:"Install with: dun skill install <target|all>"},u);return}case"install":{let y=(D[0]||"").toLowerCase();if(!y)throw new z(Au,O.USAGE);let m=await Td();if(y==="all"){let q=[];for(let X of p){let Z=await Hu(X,m);q.push({target:Z.target,path:Z.path})}k({target:"all",count:q.length,installed:q,message:`Installed DunSocial skill for ${q.length} agents.`},u);return}let U=x.get(y);if(!U)throw new z(`Unknown agent "${y}". ${Au}
232
+ Try: dun skill list`,O.USAGE);let $=await Hu(U,m);k($,u);return}default:throw new z(`Unknown skill command "${w??""}". Try: install, list, path`,O.USAGE)}}import{spawn as Ad}from"node:child_process";import{realpathSync as Hd}from"node:fs";var K="dunsocial",Pd=`https://registry.npmjs.org/${K}/latest`;function Nd(u=process.argv[1]??""){let w=u.replaceAll("\\","/");try{w=Hd(u).replaceAll("\\","/")}catch{}if(w.includes("/.npm/_npx")||w.includes("/npm/_npx")||w.includes("/_npx/"))return{packageManager:"npx",manual:`npm install -g ${K}@latest`};if(w.includes("/.pnpm/")||w.includes("/pnpm/"))return{packageManager:"pnpm",command:"pnpm",args:["add","-g",`${K}@latest`],manual:`pnpm add -g ${K}@latest`};if(w.includes("/.yarn/")||w.includes("/yarn/global"))return{packageManager:"yarn",command:"yarn",args:["global","add",`${K}@latest`],manual:`yarn global add ${K}@latest`};if(w.includes("/.bun/")||w.includes("/bun/install/global"))return{packageManager:"bun",command:"bun",args:["add","-g",`${K}@latest`],manual:`bun add -g ${K}@latest`};if(w.includes(`/node_modules/${K}/`))return{packageManager:"npm",command:"npm",args:["install","-g",`${K}@latest`],manual:`npm install -g ${K}@latest`};return{packageManager:"unknown",manual:`npm install -g ${K}@latest`}}function Md(u,w){let d=u.replace(/^v/,"").split(".").map((m)=>Number.parseInt(m,10)||0),D=w.replace(/^v/,"").split(".").map((m)=>Number.parseInt(m,10)||0),y=Math.max(d.length,D.length);for(let m=0;m<y;m++){let U=d[m]??0,$=D[m]??0;if(U>$)return 1;if(U<$)return-1}return 0}async function _d(u=fetch){let w=await u(Pd,{headers:{Accept:"application/json"}});if(!w.ok)throw new z(`Could not check npm for updates (HTTP ${w.status}). Try: npm install -g ${K}@latest`,O.NETWORK);let d=await w.json();if(!d.version)throw new z("npm registry response missing version",O.NETWORK);return d.version}function jd(u,w){return new Promise((d,D)=>{let y=Ad(u,w,{stdio:"inherit",env:process.env,windowsHide:!0,shell:process.platform==="win32"});y.on("error",(m)=>{D(new z(`Failed to run ${u}: ${m.message}. Try manually: ${u} ${w.join(" ")}`,O.NETWORK))}),y.on("close",(m)=>d(m??1))})}async function ju(u,w,d){let D=R(d,"check"),y=R(d,"force"),m=T,U=Nd(),$;try{$=await _d()}catch(Q){if(Q instanceof z)throw Q;throw new z(`Could not check npm for updates: ${Q.message}`,O.NETWORK)}let q=Md($,m)>0,X={current:m,latest:$,upToDate:!q,packageManager:U.packageManager,command:U.manual};if(!q&&!y){k(u.json?X:`Already on the latest version (${m}).`,u);return}if(D){k(u.json?{...X,updateAvailable:q}:`Update available: ${m} → ${$}
176
233
  Run: dun update
177
- Or: ${O.manual}`,U);return}if(!O.command||!O.args){V(U.json?{...Y,applied:!1,hint:O.manual}:`Update available: ${D} → ${w}
178
- Cannot auto-update this install (${O.packageManager}).
179
- Run: ${O.manual}`,U);return}if(!U.json&&!U.quiet)process.stderr.write(`Updating ${H}: ${D} → ${w} via ${O.packageManager}...
180
- `);let q=await B$(O.command,O.args);if(q!==0)throw new Z(`Update command failed (exit ${q}). Try: ${O.manual}`,Q.NETWORK);V(U.json?{...Y,applied:!0}:`Updated via ${O.packageManager}. Run dun --version to confirm.`,U)}function bU(){if(A(process.stdout))process.stdout.write(`${XU()}
181
-
182
- `);process.stdout.write(`${i}
183
- `)}async function P$(){let U=await C(),u=process.env.DUN_TOKEN||U.token,$=process.env.DUN_WORKSPACE_ID||U.defaultWorkspaceId;process.stdout.write(`${JU({token:u,workspaceId:$,config:U})}
184
- `)}async function d$(U){let u=DU(U),{group:$,action:z,nest:X,flags:D,positionals:O}=u;if(f(D)&&!$)return process.stdout.write(`${B}
185
- `),Q.OK;if($==="help"||!$&&E(D)){let J=$==="help"?z||O[0]:void 0;if(J)process.stdout.write(`${g(J)}
186
- `);else bU();return Q.OK}if(!$){if(A(process.stdout))await P$();else bU();return Q.OK}if(E(D))return process.stdout.write(`${g($)}
187
- `),Q.OK;let w=await uU({apiUrl:K(D,"api-url","apiUrl"),token:K(D,"token"),workspace:K(D,"workspace","w"),json:G(D,"json","j")?!0:void 0,debug:G(D,"debug"),quiet:G(D,"quiet","q"),yes:G(D,"yes","y")});if(f(D))return V({version:B},w),Q.OK;try{switch($){case"auth":await YU(w,z,D,O);break;case"workspace":case"workspaces":await qU(w,z,D,O);break;case"account":case"accounts":await KU(w,z??"list",D,O);break;case"post":case"posts":await GU(w,z,D,O);break;case"draft":case"drafts":await kU(w,z,D,O);break;case"media":await FU(w,z,D,O);break;case"memory":{let J=X?[X,z].filter(Boolean):z?[z]:[];await RU(w,J,D,O);break}case"personalization":case"voice":await TU(w,z,D,O);break;case"skill":case"skills":await LU(w,z,D,O);break;case"update":case"upgrade":case"self-update":await AU(w,z,D);break;case"version":V({version:B},w);break;default:throw new Z(`Unknown command "${$}". Run dun --help`,Q.USAGE)}return Q.OK}catch(J){return OU(J,w)}}var L$=await d$(process.argv.slice(2));process.exit(L$);
234
+ Or: ${U.manual}`,u);return}if(!U.command||!U.args){k(u.json?{...X,applied:!1,hint:U.manual}:`Update available: ${m} → ${$}
235
+ Cannot auto-update this install (${U.packageManager}).
236
+ Run: ${U.manual}`,u);return}if(!u.json&&!u.quiet)process.stderr.write(`Updating ${K}: ${m} → ${$} via ${U.packageManager}...
237
+ `);let Z=await jd(U.command,U.args);if(Z!==0)throw new z(`Update command failed (exit ${Z}). Try: ${U.manual}`,O.NETWORK);k(u.json?{...X,applied:!0}:`Updated via ${U.packageManager}. Run dun --version to confirm.`,u)}function Lu(){if(H(process.stdout))process.stdout.write(`${zu()}
238
+
239
+ `);process.stdout.write(`${g}
240
+ `)}async function Ld(){let u=await i(),w=process.env.DUN_TOKEN||u.token,d=process.env.DUN_WORKSPACE_ID||u.defaultWorkspaceId;process.stdout.write(`${Xu({token:w,workspaceId:d,config:u})}
241
+ `)}async function vd(u){let w=$u(u),{group:d,action:D,nest:y,flags:m,positionals:U}=w;if(C(m)&&!d)return process.stdout.write(`${T}
242
+ `),O.OK;if(d==="help"||!d&&E(m)){let q=d==="help"?D||U[0]:void 0;if(q)process.stdout.write(`${s(q)}
243
+ `);else Lu();return O.OK}if(!d){if(H(process.stdout))await Ld();else Lu();return O.OK}if(E(m))return process.stdout.write(`${s(d)}
244
+ `),O.OK;let $=await mu({apiUrl:V(m,"api-url","apiUrl"),token:V(m,"token"),workspace:V(m,"workspace","w"),json:R(m,"json","j")?!0:void 0,debug:R(m,"debug"),quiet:R(m,"quiet","q"),yes:R(m,"yes","y")});if(C(m))return k({version:T},$),O.OK;try{switch(d){case"auth":await ku($,D,m,U);break;case"workspace":case"workspaces":await Vu($,D,m,U);break;case"account":case"accounts":if(D==="reddit"){await c($,U[0],m,U.slice(1));break}await Qu($,D??"list",m,U);break;case"reddit":await c($,D,m,U);break;case"post":case"posts":await Gu($,D,m,U);break;case"draft":case"drafts":await bu($,D,m,U);break;case"media":await Ku($,D,m,U);break;case"memory":{let q=y?[y,D].filter(Boolean):D?[D]:[];await Tu($,q,m,U);break}case"personalization":case"voice":await Bu($,D,m,U);break;case"skill":case"skills":await _u($,D,m,U);break;case"update":case"upgrade":case"self-update":await ju($,D,m);break;case"version":k({version:T},$);break;default:throw new z(`Unknown command "${d}". Run dun --help`,O.USAGE)}return O.OK}catch(q){return Du(q,$)}}var ed=await vd(process.argv.slice(2));process.exit(ed);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dunsocial",
3
- "version": "0.9.0",
3
+ "version": "0.10.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",
@@ -69,6 +69,13 @@ Absolute time: `--at 2026-06-01T09:00:00Z`.
69
69
  dun posts publish --text "Hello from dun" --accounts acc_x --json
70
70
  ```
71
71
 
72
+ `publish` **queues** work (`status: scheduled`). For a terminal outcome use `--wait`:
73
+
74
+ ```bash
75
+ dun posts publish --text "Hello" --accounts acc_x --wait --json
76
+ # optional: --wait-timeout 180
77
+ ```
78
+
72
79
  ## Upload media then post
73
80
 
74
81
  ```bash
@@ -76,6 +83,78 @@ dun media upload ./shot.png --alt "Product screenshot" --json
76
83
  dun posts schedule --text "…" --accounts acc_x --media <assetId> --in 30m --json
77
84
  ```
78
85
 
86
+ Local preflight rejects images > 8MB and video > 256MB **before** upload-url. Compress video first if needed:
87
+
88
+ ```bash
89
+ ffmpeg -i input.mp4 -vf scale=-2:1080 -c:v libx264 -crf 23 -c:a aac output.mp4
90
+ ```
91
+
92
+ ## Personal LinkedIn video
93
+
94
+ No `--meta`. Upload one MP4/MOV, attach with `--media`, target a **personal** LinkedIn account (`dun accounts list --platform linkedin`). Rules: **exactly one video** or images — never mix. `linkedin-business` / Company Pages are images-only.
95
+
96
+ ```bash
97
+ dun accounts list --platform linkedin --json
98
+ dun media upload ./clip.mp4 --json
99
+ dun posts publish \
100
+ --accounts <linkedinAccountId> \
101
+ --text "Launch notes" \
102
+ --media <assetId> \
103
+ --wait \
104
+ --json
105
+ ```
106
+
107
+ ## Reddit (special)
108
+
109
+ Reddit needs platform metadata under `--meta`. Discover flairs first — many subs require a **template id**, not free text.
110
+
111
+ ```bash
112
+ dun accounts list --platform reddit --json
113
+ dun reddit flairs tamilyapping --json
114
+ # -> { id, text, textEditable }
115
+ ```
116
+
117
+ Required `metadata.reddit` fields:
118
+
119
+ | Field | Notes |
120
+ |-------|--------|
121
+ | `subreddit` | without `r/` |
122
+ | `kind` | `self` \| `link` \| `image` \| `video` |
123
+ | `title` | max 300 (sub may be stricter) |
124
+ | `flairId` | template id from `dun reddit flairs` when flair is required |
125
+
126
+ Optional: `body` (self), `url` (link), `flairText` (**only** when `textEditable` is true), `posterMediaAssetId`, `nsfw`, `spoiler`.
127
+
128
+ **Do not** send `flairText` without `flairId`. For non-editable templates, send **`flairId` alone**.
129
+
130
+ ### Worked Reddit video example
131
+
132
+ ```bash
133
+ dun media upload ./demo.mp4 --json
134
+ # assetId from response
135
+
136
+ dun reddit flairs tamilyapping --json
137
+ # pick flairId
138
+
139
+ dun posts validate \
140
+ --accounts <redditAccountId> \
141
+ --media <assetId> \
142
+ --meta '{"reddit":{"subreddit":"tamilyapping","kind":"video","title":"Approved title","flairId":"<id>"}}' \
143
+ --json
144
+
145
+ dun posts publish \
146
+ --accounts <redditAccountId> \
147
+ --media <assetId> \
148
+ --meta '{"reddit":{"subreddit":"tamilyapping","kind":"video","title":"Approved title","flairId":"<id>"}}' \
149
+ --wait \
150
+ --json
151
+ # (--text optional for Reddit when metadata.reddit.title is set)
152
+ ```
153
+
154
+ `--wait` polls until `published` or `failed` and returns `publishedPostUrl` (post permalink, not the subreddit root).
155
+
156
+ Also: `dun reddit requirements <subreddit> --json` for live rules (`is_flair_required`, title limits).
157
+
79
158
  ## Drafts / memory / threads
80
159
 
81
160
  ```bash
@@ -84,7 +163,7 @@ dun memory collections list --json
84
163
  dun memory save --collection <id> --text "…" --json
85
164
  dun memory search --prompt "…" --collections <id> --json
86
165
 
87
- # thread.json: [ {"content":"1"}, {"content":"2"} ]
166
+ # thread.json: [ {"content":"1"},{"content":"2"} ]
88
167
  dun posts schedule-thread --account acc_x --file ./thread.json --in 2h --json
89
168
  ```
90
169
 
@@ -132,7 +211,8 @@ Failure JSON:
132
211
  - Need workspace: `workspace use` or `--workspace` / `DUN_WORKSPACE_ID`
133
212
  - `--accounts` = DunSocial social account **ids**, not handles
134
213
  - Upload local files with `media upload` before attaching
135
- - Platform-specific options via `--meta '{"reddit":{…}}'` when needed
214
+ - Reddit (and some other platforms) need `--meta '{"reddit":{…}}'`
215
+ - `posts get` / list never include OAuth secrets — safe to log
136
216
  - MCP OAuth and CLI tokens are different systems
137
217
 
138
218
  ## Help
@@ -140,4 +220,5 @@ Failure JSON:
140
220
  ```bash
141
221
  dun --help
142
222
  dun posts --help
223
+ dun reddit --help
143
224
  ```