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