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