dunsocial 0.9.0 → 0.9.1
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 +57 -1
- package/dist/index.js +117 -68
- package/package.json +1 -1
- package/skills/dunsocial/SKILL.md +68 -2
package/README.md
CHANGED
|
@@ -60,6 +60,59 @@ dun posts publish \
|
|
|
60
60
|
--accounts ACCOUNT_ID
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
`publish` queues the post (`status: scheduled`). To wait until it finishes:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
dun posts publish \
|
|
67
|
+
--text "Hello from DunSocial" \
|
|
68
|
+
--accounts ACCOUNT_ID \
|
|
69
|
+
--wait
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Reddit
|
|
75
|
+
|
|
76
|
+
Reddit needs platform options via `--meta`. Many subreddits also require a flair **template id**.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# 1. Upload media first (videos must be under 256MB)
|
|
80
|
+
dun media upload ./demo.mp4 --json
|
|
81
|
+
|
|
82
|
+
# 2. List flairs for the subreddit (use id, not just the label text)
|
|
83
|
+
dun reddit flairs tamilyapping --json
|
|
84
|
+
|
|
85
|
+
# 3. Optional dry-run against subreddit rules
|
|
86
|
+
dun posts validate \
|
|
87
|
+
--accounts REDDIT_ACCOUNT_ID \
|
|
88
|
+
--media ASSET_ID \
|
|
89
|
+
--meta '{"reddit":{"subreddit":"tamilyapping","kind":"video","title":"Your title","flairId":"FLAIR_ID"}}' \
|
|
90
|
+
--json
|
|
91
|
+
|
|
92
|
+
# 4. Publish and wait for the real permalink
|
|
93
|
+
dun posts publish \
|
|
94
|
+
--accounts REDDIT_ACCOUNT_ID \
|
|
95
|
+
--media ASSET_ID \
|
|
96
|
+
--meta '{"reddit":{"subreddit":"tamilyapping","kind":"video","title":"Your title","flairId":"FLAIR_ID"}}' \
|
|
97
|
+
--wait \
|
|
98
|
+
--json
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`--meta.reddit` fields:
|
|
102
|
+
|
|
103
|
+
| Field | Required | Notes |
|
|
104
|
+
|-------|----------|--------|
|
|
105
|
+
| `subreddit` | yes | without `r/` |
|
|
106
|
+
| `kind` | yes | `self`, `link`, `image`, or `video` |
|
|
107
|
+
| `title` | yes | |
|
|
108
|
+
| `flairId` | when the sub requires flair | from `dun reddit flairs` |
|
|
109
|
+
| `url` | for `link` | |
|
|
110
|
+
| `flairText` | no | only if that flair's `textEditable` is true |
|
|
111
|
+
|
|
112
|
+
Prefer `flairId` alone. Sending `flairText` without `flairId` always fails.
|
|
113
|
+
|
|
114
|
+
Also useful: `dun reddit requirements <subreddit> --json`.
|
|
115
|
+
|
|
63
116
|
---
|
|
64
117
|
|
|
65
118
|
## Automated posting (CI)
|
|
@@ -89,6 +142,8 @@ dun --help
|
|
|
89
142
|
dun auth status
|
|
90
143
|
dun posts list
|
|
91
144
|
dun media upload ./photo.png
|
|
145
|
+
dun reddit flairs some-subreddit
|
|
146
|
+
dun posts validate --accounts ACCOUNT_ID --meta '{"reddit":{…}}'
|
|
92
147
|
dun drafts create --text "Idea for later"
|
|
93
148
|
dun personalization get
|
|
94
149
|
dun personalization set --description "Concise, direct, no fluff."
|
|
@@ -96,8 +151,9 @@ dun personalization set --description "Concise, direct, no fluff."
|
|
|
96
151
|
|
|
97
152
|
Tips:
|
|
98
153
|
|
|
99
|
-
- Help for any command: `dun posts --help`
|
|
154
|
+
- Help for any command: `dun posts --help` / `dun reddit --help`
|
|
100
155
|
- Structured output for scripts: add `--json`
|
|
156
|
+
- Platform-specific options (Reddit, etc.): `--meta '{…}'`
|
|
101
157
|
- Prefer ChatGPT or Claude **in the browser**? Use [MCP](https://docs.dunsocial.com/mcp) instead of the CLI
|
|
102
158
|
- Personalization is workspace-wide AI voice (Settings → Personalization). Updating it needs a token with the `workspace:write` scope
|
|
103
159
|
|
package/dist/index.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{readFileSync as
|
|
3
|
-
`,"utf8");try{await
|
|
4
|
-
`);return}if(
|
|
5
|
-
`);return}if(typeof
|
|
6
|
-
`);return}process.stdout.write(`${
|
|
7
|
-
`)}function
|
|
8
|
-
`);else if(process.stderr.write(`error: ${
|
|
9
|
-
`),
|
|
10
|
-
`);return
|
|
11
|
-
`)}if(
|
|
12
|
-
${
|
|
13
|
-
`)}return String(
|
|
14
|
-
`);return`${
|
|
15
|
-
${
|
|
16
|
-
`);return}process.stderr.write(`[debug] ${
|
|
17
|
-
`)}function
|
|
18
|
-
${
|
|
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)}
|
|
4
|
+
`);return}if(u===void 0||u===null){process.stdout.write(`ok
|
|
5
|
+
`);return}if(typeof u==="string"){process.stdout.write(`${u}
|
|
6
|
+
`);return}process.stdout.write(`${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}
|
|
18
|
+
${D}`}var g=`dun - schedule & publish social posts
|
|
19
19
|
|
|
20
20
|
Usage:
|
|
21
21
|
dun <command> [flags]
|
|
22
22
|
|
|
23
|
-
${
|
|
23
|
+
${K("\uD83D\uDD10","Auth")}
|
|
24
24
|
dun auth login # device-code browser flow
|
|
25
25
|
dun auth login --token <token> # CI / headless
|
|
26
26
|
dun auth login --no-browser
|
|
@@ -28,38 +28,49 @@ ${P("\uD83D\uDD10","Auth")}
|
|
|
28
28
|
dun auth whoami
|
|
29
29
|
dun auth status
|
|
30
30
|
|
|
31
|
-
${
|
|
31
|
+
${K("\uD83D\uDCC1","Workspaces")}
|
|
32
32
|
dun workspace list
|
|
33
33
|
dun workspace use <id|slug>
|
|
34
34
|
dun workspace current
|
|
35
35
|
|
|
36
|
-
${
|
|
37
|
-
dun accounts list [--platform x]
|
|
36
|
+
${K("\uD83D\uDD17","Accounts")}
|
|
37
|
+
dun accounts list [--platform x|reddit|...]
|
|
38
38
|
|
|
39
|
-
${
|
|
39
|
+
${K("\uD83D\uDFE0","Reddit")}
|
|
40
|
+
dun reddit flairs <subreddit> # template id + text + textEditable
|
|
41
|
+
dun reddit requirements <subreddit> # live post rules (flair required, ...)
|
|
42
|
+
# alias: dun accounts reddit flairs <subreddit>
|
|
43
|
+
|
|
44
|
+
${K("\uD83D\uDCDD","Posts")}
|
|
40
45
|
dun posts list [--status scheduled|published|failed|draft|cancelled]
|
|
41
46
|
dun posts get <id>
|
|
42
|
-
dun posts
|
|
43
|
-
dun posts
|
|
47
|
+
dun posts validate --accounts <id> [--media id] --meta '{...}'
|
|
48
|
+
dun posts schedule --text "..." --accounts <id,id> (--at ISO | --in 2h) [--media id,id] [--meta '{...}']
|
|
49
|
+
dun posts publish --text "..." --accounts <id,id> [--media id,id] [--meta '{...}'] [--natural] [--wait]
|
|
44
50
|
dun posts reschedule <id> --at ISO|--in 2h [--text "..."]
|
|
45
51
|
dun posts cancel <id>
|
|
46
52
|
dun posts delete <id> [--yes]
|
|
47
53
|
dun posts x-cap
|
|
48
54
|
|
|
49
|
-
|
|
55
|
+
# Reddit --meta example (video):
|
|
56
|
+
# --meta '{"reddit":{"subreddit":"tamilyapping","kind":"video","title":"...","flairId":"..."}}'
|
|
57
|
+
# kinds: self | link | image | video
|
|
58
|
+
# publish queues async work; without --wait, status scheduled means queued
|
|
59
|
+
|
|
60
|
+
${K("\uD83D\uDCC4","Drafts")}
|
|
50
61
|
dun drafts list
|
|
51
62
|
dun drafts get <id>
|
|
52
63
|
dun drafts create --text "..." [--platforms x,linkedin] [--accounts id,id]
|
|
53
64
|
dun drafts update <id> --text "..."
|
|
54
65
|
dun drafts delete <id> [--yes]
|
|
55
66
|
|
|
56
|
-
${
|
|
67
|
+
${K("\uD83D\uDDBC️","Media")}
|
|
57
68
|
dun media list
|
|
58
69
|
dun media get <id>
|
|
59
|
-
dun media upload <file> [--alt "..."]
|
|
70
|
+
dun media upload <file> [--alt "..."] # local preflight: images <=8MB, video <=256MB
|
|
60
71
|
dun media delete <id> [--yes]
|
|
61
72
|
|
|
62
|
-
${
|
|
73
|
+
${K("\uD83E\uDDE0","Memory")}
|
|
63
74
|
dun memory collections list
|
|
64
75
|
dun memory collections create --name "..." [--color blue] [--private]
|
|
65
76
|
dun memory list --collection <id>
|
|
@@ -67,7 +78,7 @@ ${P("\uD83E\uDDE0","Memory")}
|
|
|
67
78
|
dun memory search --prompt "..." --collections <id,id> [--top-k 5]
|
|
68
79
|
dun memory delete <id> --collection <id> [--yes]
|
|
69
80
|
|
|
70
|
-
${
|
|
81
|
+
${K("\uD83C\uDF99️","Personalization")}
|
|
71
82
|
# workspace AI voice
|
|
72
83
|
dun personalization get
|
|
73
84
|
dun personalization set --description "..."
|
|
@@ -75,21 +86,21 @@ ${P("\uD83C\uDF99️","Personalization")}
|
|
|
75
86
|
dun personalization set --clear
|
|
76
87
|
# requires PAT scope workspace:write for set (owner/admin)
|
|
77
88
|
|
|
78
|
-
${
|
|
89
|
+
${K("\uD83E\uDDE9","Agent skills")}
|
|
79
90
|
dun skill install <agent|all>
|
|
80
91
|
dun skill list
|
|
81
92
|
dun skill path
|
|
82
93
|
# agents: claude codex cursor pi opencode agents copilot windsurf
|
|
83
94
|
# goose gemini amp grok continue antigravity
|
|
84
95
|
|
|
85
|
-
${
|
|
96
|
+
${K("⬆️","Update")}
|
|
86
97
|
dun update # upgrade to latest from npm
|
|
87
98
|
dun update --check # report only
|
|
88
99
|
dun update --force # reinstall latest even if current
|
|
89
100
|
# aliases: upgrade, self-update
|
|
90
101
|
# detects npm / bun / pnpm / yarn; prints command for npx
|
|
91
102
|
|
|
92
|
-
${
|
|
103
|
+
${K("⚙️","Global flags")}
|
|
93
104
|
--json Machine-readable output (default when non-TTY / CI)
|
|
94
105
|
--workspace <id> Override workspace for this command
|
|
95
106
|
--api-url <url> Override API base (default https://api.dunsocial.com)
|
|
@@ -100,14 +111,14 @@ ${P("⚙️","Global flags")}
|
|
|
100
111
|
--help, -h Show help
|
|
101
112
|
--version, -v Show version
|
|
102
113
|
|
|
103
|
-
${
|
|
114
|
+
${K("\uD83C\uDF31","Env")}
|
|
104
115
|
DUN_TOKEN DUN_WORKSPACE_ID DUN_API_URL DUN_CONFIG DUN_JSON=1 DUN_DEBUG=1
|
|
105
116
|
|
|
106
|
-
${
|
|
117
|
+
${K("\uD83D\uDEAA","Exit codes")}
|
|
107
118
|
0 ok | 1 usage | 2 auth | 3 forbidden | 4 validation | 5 not found | 6 rate limit | 7 network
|
|
108
119
|
|
|
109
120
|
Docs: https://dunsocial.com/docs/cli
|
|
110
|
-
`;function
|
|
121
|
+
`;function r(u){return{auth:`dun auth
|
|
111
122
|
|
|
112
123
|
login [--token] Device-code browser login (default) or --token for CI
|
|
113
124
|
logout Clear local config
|
|
@@ -118,17 +129,53 @@ Docs: https://dunsocial.com/docs/cli
|
|
|
118
129
|
list List workspaces you belong to
|
|
119
130
|
use <id|slug> Set default workspace
|
|
120
131
|
current Show default workspace
|
|
121
|
-
`,workspaces:"Alias of workspace",accounts
|
|
132
|
+
`,workspaces:"Alias of workspace",accounts:`dun accounts list [--platform <name>]
|
|
133
|
+
dun accounts reddit flairs <subreddit> # alias of dun reddit flairs
|
|
134
|
+
`,reddit:`dun reddit
|
|
135
|
+
|
|
136
|
+
flairs <subreddit> List flair template ids for the connected Reddit account
|
|
137
|
+
requirements <subreddit> Live post requirements (flair required, title limits, ...)
|
|
138
|
+
|
|
139
|
+
Output fields for flairs: id (flair_template_id), text, textEditable
|
|
140
|
+
|
|
141
|
+
Example:
|
|
142
|
+
dun reddit flairs tamilyapping --json
|
|
143
|
+
`,posts:`dun posts
|
|
122
144
|
|
|
123
145
|
list [--status] [--account] [--limit] [--offset]
|
|
124
146
|
get <id>
|
|
125
|
-
|
|
126
|
-
|
|
147
|
+
validate --accounts [--text] [--media] --meta Dry-run Reddit/media rules before enqueue
|
|
148
|
+
schedule --text --accounts (--at|--in) [--media] [--meta]
|
|
149
|
+
publish --text --accounts [--media] [--meta] [--natural] [--wait] [--wait-timeout 120]
|
|
127
150
|
reschedule <id> (--at|--in) [--text]
|
|
128
151
|
cancel <id>
|
|
129
152
|
delete <id> [--yes]
|
|
130
153
|
x-cap
|
|
131
|
-
|
|
154
|
+
|
|
155
|
+
Platform metadata (--meta / --metadata / --meta-json):
|
|
156
|
+
Reddit (required when publishing to a Reddit account):
|
|
157
|
+
{"reddit":{"subreddit":"NAME","kind":"self|link|image|video","title":"...","flairId":"..."}}
|
|
158
|
+
Optional reddit fields: body, url (link), flairText (only if textEditable), posterMediaAssetId, nsfw, spoiler
|
|
159
|
+
|
|
160
|
+
Reddit happy path:
|
|
161
|
+
1. dun media upload ./video.mp4 --json # rejects >256MB locally
|
|
162
|
+
2. dun reddit flairs SUB --json # pick flairId
|
|
163
|
+
3. dun posts validate --accounts ID --media ASSET --meta '...' --json
|
|
164
|
+
4. dun posts publish --accounts ID --media ASSET --meta '...' --wait --json
|
|
165
|
+
|
|
166
|
+
Notes:
|
|
167
|
+
- publish without --wait returns status scheduled (queued). Poll get or use --wait.
|
|
168
|
+
- Prefer flairId alone. flairText without flairId always fails.
|
|
169
|
+
`,drafts:"dun drafts list|get|create|update|delete",media:`dun media
|
|
170
|
+
|
|
171
|
+
list|get|upload|delete
|
|
172
|
+
upload <file> [--alt] [--mime]
|
|
173
|
+
|
|
174
|
+
Local preflight before upload-url:
|
|
175
|
+
images <= 8MB (jpeg/png/gif/webp)
|
|
176
|
+
video <= 256MB (mp4/mov/webm)
|
|
177
|
+
Oversized video error includes an ffmpeg compress one-liner.
|
|
178
|
+
`,memory:`dun memory collections list|create
|
|
132
179
|
dun memory list|save|search|delete`,personalization:`dun personalization
|
|
133
180
|
|
|
134
181
|
get Show workspace AI voice profile
|
|
@@ -147,41 +194,43 @@ dun skill path`,update:`dun update
|
|
|
147
194
|
--force Reinstall latest even if versions match
|
|
148
195
|
|
|
149
196
|
Aliases: dun upgrade, dun self-update
|
|
150
|
-
`,upgrade:"Alias of update","self-update":"Alias of update"}[
|
|
151
|
-
`),
|
|
152
|
-
`).map((
|
|
153
|
-
CLI v${B}`}var
|
|
154
|
-
`)}import{spawn as
|
|
155
|
-
Next: dun workspace list && dun workspace use <id|slug>`}async function
|
|
156
|
-
${
|
|
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()}
|
|
157
204
|
|
|
158
205
|
`);else process.stderr.write(`
|
|
159
206
|
DunSocial CLI login
|
|
160
207
|
`),process.stderr.write(`───────────────────
|
|
161
208
|
`);process.stderr.write(`In your browser, open:
|
|
162
|
-
${
|
|
209
|
+
${D.verificationUrlComplete}
|
|
163
210
|
|
|
164
|
-
`),process.stderr.write(`Or go to ${
|
|
165
|
-
`),process.stderr.write(` ${
|
|
211
|
+
`),process.stderr.write(`Or go to ${D.verificationUrl} and enter code:
|
|
212
|
+
`),process.stderr.write(` ${D.userCode}
|
|
166
213
|
|
|
167
214
|
`),process.stderr.write(`Waiting for approval...
|
|
168
|
-
`)}else process.stderr.write(JSON.stringify({ok:!0,phase:"waiting",userCode:
|
|
169
|
-
`);if(X&&process.stdout.isTTY)try{await u$(z.verificationUrlComplete)}catch{}let D=Math.max(3,z.interval||5)*1000,O=Date.now()+(z.expiresIn||900)*1000;while(Date.now()<O){await $$(D);let w=await $.post("/api/cli/auth/poll",{deviceCode:z.deviceCode},{auth:!1});if(w.status==="pending")continue;if(w.status==="expired")throw new Z("Login expired. Run dun auth login again.",Q.AUTH);if(w.status==="denied")throw new Z("Login was denied in the browser.",Q.AUTH);if(w.status==="approved"){let J=w.token,Y={...U,token:J},q=k(Y),y=await q.get("/api/user/profile"),W={...U.config,apiUrl:U.apiUrl,token:J,defaultWorkspaceId:w.workspaceId||U.config.defaultWorkspaceId,defaultWorkspaceSlug:U.config.defaultWorkspaceSlug,defaultWorkspaceName:U.config.defaultWorkspaceName};if(w.workspaceId)try{let M=(await q.get("/api/workspaces")).find((jU)=>jU.id===w.workspaceId);if(M)W.defaultWorkspaceId=M.id,W.defaultWorkspaceSlug=M.slug,W.defaultWorkspaceName=M.name}catch{}await I(W);let N=Boolean(W.defaultWorkspaceId);if(!U.json&&A(process.stdout)){V(h(N),U);return}V({user:{id:y.id,name:y.name,email:y.email},workspaceId:W.defaultWorkspaceId??null,configPath:j(),method:"device",message:h(N)},U);return}}throw new Z("Login timed out. Run dun auth login again.",Q.AUTH)}async function aU(U){if(await $U(),!U.json&&A(process.stdout)){V("Logged out. Config cleared.",U);return}V({message:"Logged out. Config cleared.",configPath:j()},U)}async function tU(U){F(U);let $=await k(U).get("/api/user/profile");V({id:$.id,name:$.name,email:$.email,image:$.image??null,apiUrl:U.apiUrl,workspaceId:U.workspaceId??null},U)}async function U$(U){let u=Boolean(U.token),$=null,z=null;if(u)try{$=await k(U).get("/api/user/profile")}catch(X){z=X instanceof Error?X.message:String(X)}V({authenticated:Boolean($),tokenPresent:u,apiUrl:U.apiUrl,workspaceId:U.workspaceId??null,workspaceSlug:U.config.defaultWorkspaceSlug??null,workspaceName:U.config.defaultWorkspaceName??null,configPath:j(),user:$?{id:$.id,name:$.name,email:$.email}:null,error:z},U)}function $$(U){return new Promise((u)=>setTimeout(u,U))}async function u$(U){let u=process.platform,$=u==="darwin"?["open",U]:u==="win32"?["cmd","/c","start","",U]:["xdg-open",U];await new Promise((z,X)=>{let D=cU($[0],$.slice(1),{stdio:"ignore",windowsHide:!0});D.on("error",X),D.on("close",()=>z())})}async function qU(U,u,$,z){F(U);let X=k(U);switch(u){case"list":case"ls":{let D=await X.get("/api/workspaces");V(D.map((O)=>({id:O.id,name:O.name,slug:O.slug,role:O.role,timezone:O.timezone,plan:O.subscription?.planName??null,status:O.subscription?.status??null})),{...U,emptyMessage:"No workspaces yet."});return}case"use":{let D=z[0];if(!D)throw new Z("Usage: dun workspace use <id|slug>",Q.USAGE);let w=(await X.get("/api/workspaces")).find((J)=>J.id===D||J.slug===D);if(!w)throw new Z(`Workspace not found: ${D}. Run dun workspace list`,Q.NOT_FOUND);await I({...U.config,apiUrl:U.apiUrl,token:U.token,defaultWorkspaceId:w.id,defaultWorkspaceSlug:w.slug,defaultWorkspaceName:w.name}),V({id:w.id,name:w.name,slug:w.slug,role:w.role,message:`Default workspace set to ${w.name}`},U);return}case"current":{if(!U.workspaceId&&!U.config.defaultWorkspaceId)throw new Z("No workspace selected. Run dun workspace use <id|slug>",Q.VALIDATION);let D=U.workspaceId||U.config.defaultWorkspaceId;try{let w=(await X.get("/api/workspaces")).find((J)=>J.id===D||J.slug===D);if(w){V({id:w.id,name:w.name,slug:w.slug,role:w.role,timezone:w.timezone},U);return}}catch{}V({id:D,name:U.config.defaultWorkspaceName??null,slug:U.config.defaultWorkspaceSlug??null},U);return}default:throw new Z(`Unknown workspace command "${u??""}". Try: list, use, current`,Q.USAGE)}}async function KU(U,u,$,z){F(U),T(U);let X=k(U);switch(u){case void 0:case"list":case"ls":{let D=await X.get("/api/social-accounts",{workspace:!0}),O=K($,"platform","p")?.toLowerCase(),w=O?D.filter((J)=>{let Y=(J.providerName||"").toLowerCase();return({x:["x","twitter"],twitter:["x","twitter"],linkedin:["linkedin"],"linkedin-business":["linkedin business","linkedin-business","linkedin_business"],bluesky:["bluesky"],threads:["threads"],reddit:["reddit"],pinterest:["pinterest"],instagram:["instagram"],youtube:["youtube"],tiktok:["tiktok"]}[O]??[O]).some((W)=>Y.includes(W))}):D;V(w.map((J)=>({id:J.id,providerName:J.providerName,username:J.username??null,displayName:J.displayName??null,isConnected:J.isConnected,expiresAt:J.expiresAt??null})),{...U,emptyMessage:O?`No ${O} accounts connected. Connect one in the DunSocial dashboard.`:"No accounts connected. Connect one in the DunSocial dashboard."});return}default:throw new Z(`Unknown accounts command "${u}". Try: list`,Q.USAGE)}}import{readFileSync as O$}from"node:fs";var D$=/^(\d+)\s*(s|sec|secs|second|seconds|m|min|mins|minute|minutes|h|hr|hrs|hour|hours|d|day|days|w|week|weeks)$/i;function o(U,u=new Date){let $=U.trim();if(!$)throw new Z("Empty time value",Q.VALIDATION);let z=$.match(D$);if(z){let O=Number(z[1]),w=z[2].toLowerCase(),J=w$(w)*O;return new Date(u.getTime()+J)}let X=$.match(/^in\s+(.+)$/i);if(X)return o(X[1],u);let D=new Date($);if(Number.isNaN(D.getTime()))throw new Z(`Invalid time "${U}". Use ISO-8601 (2026-06-01T09:00:00Z) or relative (2h, 30m, 1d).`,Q.VALIDATION);return D}function w$(U){switch(U){case"s":case"sec":case"secs":case"second":case"seconds":return 1000;case"m":case"min":case"mins":case"minute":case"minutes":return 60000;case"h":case"hr":case"hrs":case"hour":case"hours":return 3600000;case"d":case"day":case"days":return 86400000;case"w":case"week":case"weeks":return 604800000;default:throw new Z(`Unknown time unit: ${U}`,Q.VALIDATION)}}function S(U){if(U.at&&U.in)throw new Z("Pass only one of --at or --in",Q.VALIDATION);if(U.at)return o(U.at).toISOString();if(U.in)return o(U.in).toISOString();throw new Z("Schedule time required. Pass --at <iso> or --in <duration> (e.g. 2h).",Q.VALIDATION)}function WU(U){if(!U)return[];return U.split(",").map((u)=>u.trim()).filter(Boolean)}function yU(U){let u=K(U,"text","content","c","t");if(!u)throw new Z("Missing --text",Q.VALIDATION);return u}function x(U){let u=L(U,"accounts","account","a")??WU(K(U,"accounts","account","a"));if(!u||u.length===0)throw new Z("Missing --accounts <id,id>",Q.VALIDATION);return u}function c(U){return L(U,"media","media-urls")??WU(K(U,"media","media-urls"))}function _(U){let u=K(U,"meta","metadata","meta-json");if(!u)return;try{let $=JSON.parse(u);if(!$||typeof $!=="object"||Array.isArray($))throw Error("metadata must be a JSON object");return $}catch($){throw new Z(`Invalid --meta JSON: ${$.message}`,Q.VALIDATION)}}async function GU(U,u,$,z){F(U),T(U);let X=k(U);switch(u){case"list":case"ls":{let D=await X.get("/api/posts",{workspace:!0,query:{status:K($,"status","s"),socialAccountId:K($,"account","social-account"),groupId:K($,"group"),limit:d($,"limit")??50,offset:d($,"offset")??0}});V(D,{...U,emptyMessage:"No posts yet. Schedule one with dun posts schedule."});return}case"get":{let D=z[0];if(!D)throw new Z("Usage: dun posts get <id>",Q.USAGE);let O=await X.get(`/api/posts/${D}`,{workspace:!0});V(O,U);return}case"schedule":{let D=yU($),O=x($),w=S({at:K($,"at"),in:K($,"in")}),J=await X.post("/api/posts/schedule",{content:D,socialAccountIds:O,scheduledAt:w,mediaUrls:c($),metadata:_($)??{}},{workspace:!0});V(J,U);return}case"publish":case"publish-now":{let D=yU($),O=x($),w=await X.post("/api/posts/publish-now",{content:D,socialAccountIds:O,mediaUrls:c($),metadata:_($)??{},naturalPosting:G($,"natural","natural-posting")},{workspace:!0});V(w,U);return}case"reschedule":{let D=z[0];if(!D)throw new Z("Usage: dun posts reschedule <id> --at|--in",Q.USAGE);let w={scheduledAt:S({at:K($,"at"),in:K($,"in")})},J=K($,"text","content","c","t");if(J)w.content=J;let Y=c($);if(Y.length)w.mediaUrls=Y;let q=_($);if(q)w.metadata=q;if(!w.content){let W=await X.get(`/api/posts/${D}`,{workspace:!0});if(w.content=W.content,!w.metadata&&W.metadata)w.metadata=W.metadata;if(!w.mediaUrls&&W.mediaUrls)w.mediaUrls=W.mediaUrls}let y=await X.post(`/api/posts/${D}/reschedule`,w,{workspace:!0});V(y,U);return}case"cancel":{let D=z[0];if(!D)throw new Z("Usage: dun posts cancel <id>",Q.USAGE);let O=await X.post(`/api/posts/${D}/cancel`,void 0,{workspace:!0});V(O??{id:D,status:"cancelled"},U);return}case"delete":case"rm":{let D=z[0];if(!D)throw new Z("Usage: dun posts delete <id> [--yes]",Q.USAGE);if(!U.yes&&!G($,"yes","y"))throw new Z("Refusing to delete without --yes (or CI/yes mode)",Q.VALIDATION);let O=await X.delete(`/api/posts/${D}`,void 0,{workspace:!0});V(O??{id:D,deleted:!0},U);return}case"x-cap":case"xcap":{let D=await X.get("/api/posts/x-cap-usage",{workspace:!0});V(D,U);return}case"schedule-thread":case"publish-thread":case"publish-thread-now":{let D=K($,"file","f"),O=K($,"account","accounts","a")??x($)[0];if(!O)throw new Z("Missing --account <xAccountId>",Q.VALIDATION);let w;if(D){let Y;try{Y=O$(D,"utf8")}catch(y){throw new Z(`Could not read file: ${y.message}`,Q.VALIDATION)}let q=JSON.parse(Y);if(Array.isArray(q))w=q;else if(q&&typeof q==="object"&&Array.isArray(q.tweets))w=q.tweets;else throw new Z("Thread file must be an array of tweets or { tweets: [] }",Q.VALIDATION)}else{let Y=K($,"text","content");if(!Y)throw new Z("Provide --file thread.json (array of {content}) or informal --text with || separators is not enough alone for threads - use --file",Q.VALIDATION);w=Y.split("||").map((q)=>({content:q.trim()})).filter((q)=>q.content)}if(w.length<2)throw new Z("Threads require at least 2 tweets",Q.VALIDATION);if(u==="schedule-thread"){let Y=S({at:K($,"at"),in:K($,"in")}),q=await X.post("/api/posts/schedule-thread",{socialAccountId:O,tweets:w,scheduledAt:Y,metadata:_($)??{}},{workspace:!0});V(q,U);return}let J=await X.post("/api/posts/publish-thread-now",{socialAccountId:O,tweets:w,metadata:_($)??{}},{workspace:!0});V(J,U);return}default:throw new Z(`Unknown posts command "${u??""}". Try: list, get, schedule, publish, reschedule, cancel, delete, x-cap, schedule-thread, publish-thread`,Q.USAGE)}}async function kU(U,u,$,z){F(U),T(U);let X=k(U);switch(u){case"list":case"ls":{let D=await X.get("/api/drafts",{workspace:!0,query:{limit:d($,"limit")??50,offset:d($,"offset")??0}});V(D,{...U,emptyMessage:'No drafts yet. Create one with dun drafts create --text "..."'});return}case"get":{let D=z[0];if(!D)throw new Z("Usage: dun drafts get <id>",Q.USAGE);let O=await X.get(`/api/drafts/${D}`,{workspace:!0});V(O,U);return}case"create":{let D=K($,"text","content","c","t");if(!D)throw new Z("Missing --text",Q.VALIDATION);let O=await X.post("/api/drafts",{content:D,name:K($,"name","n"),socialAccountIds:L($,"accounts","account","a")??[],selectedPlatforms:L($,"platforms","platform","p")??[],mediaUrls:L($,"media")??[]},{workspace:!0});V(O,U);return}case"update":{let D=z[0];if(!D)throw new Z("Usage: dun drafts update <id> --text ...",Q.USAGE);let O={id:D},w=K($,"text","content","c","t");if(w)O.content=w;let J=K($,"name","n");if(J!==void 0)O.name=J;let Y=L($,"accounts","account","a");if(Y)O.socialAccountIds=Y;let q=L($,"platforms","platform","p");if(q)O.selectedPlatforms=q;let y=L($,"media");if(y)O.mediaUrls=y;let W=await X.patch(`/api/drafts/${D}`,O,{workspace:!0});V(W,U);return}case"delete":case"rm":{let D=z[0];if(!D)throw new Z("Usage: dun drafts delete <id> [--yes]",Q.USAGE);if(!U.yes&&!G($,"yes","y"))throw new Z("Refusing to delete without --yes",Q.VALIDATION);let O=await X.delete(`/api/drafts/${D}`,void 0,{workspace:!0});V(O??{id:D,deleted:!0},U);return}default:throw new Z(`Unknown drafts command "${u??""}". Try: list, get, create, update, delete`,Q.USAGE)}}import{readFileSync as z$,existsSync as Q$,statSync as X$}from"node:fs";import{basename as Z$}from"node:path";function J$(U){let u=U.split(".").pop()?.toLowerCase()??"";return{jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",mp4:"video/mp4",mov:"video/quicktime",webm:"video/webm"}[u]??"application/octet-stream"}async function FU(U,u,$,z){F(U),T(U);let X=k(U);switch(u){case"list":case"ls":{let D=await X.get("/api/media",{workspace:!0,query:{limit:d($,"limit")??50,offset:d($,"offset")??0}});V(D,{...U,emptyMessage:"No media yet. Upload with dun media upload <file>."});return}case"get":{let D=z[0];if(!D)throw new Z("Usage: dun media get <id>",Q.USAGE);let O=await X.get(`/api/media/${D}`,{workspace:!0});V(O,U);return}case"upload":{let D=z[0]||K($,"file","f");if(!D)throw new Z('Usage: dun media upload <file> [--alt "..."]',Q.USAGE);if(!Q$(D))throw new Z(`File not found: ${D}`,Q.VALIDATION);let O=Z$(D),w=X$(D).size,J=K($,"mime","type","content-type")||J$(O),Y=K($,"alt","alt-text"),q=await X.post("/api/media/upload-url",{filename:O,mimeType:J,fileSize:w},{workspace:!0});m(U.debug,`PUT ${q.uploadUrl}`,{storageKey:q.storageKey,fileSize:w});let y=z$(D),W;try{W=await fetch(q.uploadUrl,{method:"PUT",headers:{"Content-Type":J,"Content-Length":String(w)},body:y})}catch(b){throw new Z(`Upload PUT failed: ${b.message}`,Q.NETWORK)}if(!W.ok){let b=await W.text().catch(()=>"");throw new Z(`Upload PUT failed with HTTP ${W.status}${b?`: ${b.slice(0,200)}`:""}`,Q.NETWORK)}let N=await X.post("/api/media/complete",{storageKey:q.storageKey,originalFilename:O,mimeType:J,fileSize:w,altText:Y},{workspace:!0});V(N,U);return}case"delete":case"rm":{let D=z[0];if(!D)throw new Z("Usage: dun media delete <id> [--yes]",Q.USAGE);if(!U.yes&&!G($,"yes","y"))throw new Z("Refusing to delete without --yes",Q.VALIDATION);let O=await X.delete(`/api/media/${D}`,void 0,{workspace:!0});V(O??{id:D,deleted:!0},U);return}default:throw new Z(`Unknown media command "${u??""}". Try: list, get, upload, delete`,Q.USAGE)}}async function RU(U,u,$,z){F(U),T(U);let X=k(U),D=u[0];if(D==="collections"||D==="collection"){let O=u[1]||"list";switch(O){case"list":case"ls":{let w=await X.get("/api/memory/collections",{workspace:!0});V(w,{...U,emptyMessage:'No memory collections yet. Create one with dun memory collections create --name "..."'});return}case"create":{let w=K($,"name","n");if(!w)throw new Z("Missing --name",Q.VALIDATION);let J=await X.post("/api/memory/collections",{name:w,color:K($,"color")??"blue",isPrivate:G($,"private")},{workspace:!0});V(J,U);return}case"get":{let w=u[2]||z[0];if(!w)throw new Z("Usage: dun memory collections get <id>",Q.USAGE);let J=await X.get(`/api/memory/collections/${w}`,{workspace:!0});V(J,U);return}default:throw new Z(`Unknown memory collections command "${O}". Try: list, create, get`,Q.USAGE)}}switch(D){case"list":case"ls":{let O=K($,"collection","collection-id","c");if(!O)throw new Z("Missing --collection <id>",Q.VALIDATION);let w=await X.get("/api/memory",{workspace:!0,query:{collectionId:O,limit:d($,"limit")??100}});V(w,{...U,emptyMessage:"No memories in this collection."});return}case"save":case"add":{let O=K($,"collection","collection-id","c"),w=K($,"text","t");if(!O)throw new Z("Missing --collection <id>",Q.VALIDATION);if(!w)throw new Z("Missing --text",Q.VALIDATION);let J=await X.post("/api/memory",{collectionId:O,text:w},{workspace:!0});V(J,U);return}case"search":{let O=K($,"prompt","q","query","text"),w=L($,"collections","collection","collection-ids","c")??[];if(!O)throw new Z("Missing --prompt",Q.VALIDATION);if(w.length===0)throw new Z("Missing --collections <id,id>",Q.VALIDATION);let J=await X.post("/api/memory/search",{prompt:O,collectionIds:w,topK:d($,"top-k","topk","k")??5},{workspace:!0});V(J,U);return}case"delete":case"rm":{let O=u[1]||z[0],w=K($,"collection","collection-id","c");if(!O)throw new Z("Usage: dun memory delete <id> --collection <id>",Q.USAGE);if(!w)throw new Z("Missing --collection <id>",Q.VALIDATION);if(!U.yes&&!G($,"yes","y"))throw new Z("Refusing to delete without --yes",Q.VALIDATION);let J=await X.delete(`/api/memory/${O}`,{collectionId:w,memoryId:O},{workspace:!0});V(J??{id:O,deleted:!0},U);return}default:throw new Z(`Unknown memory command "${D??""}". Try: collections, list, save, search, delete`,Q.USAGE)}}import{readFileSync as V$}from"node:fs";async function TU(U,u,$,z){F(U);let X=T(U),D=k(U),O=`/api/workspaces/${X}/ai-voice-settings`;switch(u){case"get":case"show":case void 0:{let w=await D.get(O,{workspace:!0});V(w??{personalization:null,hint:'No personalization set. Use dun personalization set --description "..."'},U);return}case"set":case"update":{let w=G($,"clear"),J=K($,"description","d"),Y=K($,"file","f");if([w,Boolean(J),Boolean(Y)].filter(Boolean).length!==1)throw new Z("Use exactly one of --description, --file, or --clear",Q.VALIDATION);let y;if(w)y=null;else if(Y){let N;try{N=V$(Y,"utf8").trim()}catch(b){throw new Z(`Could not read file: ${b.message}`,Q.VALIDATION)}if(!N)throw new Z("File is empty",Q.VALIDATION);y={description:N}}else{let N=J.trim();if(!N)throw new Z("--description cannot be empty",Q.VALIDATION);y={description:N}}let W=await D.patch(O,y,{workspace:!0});V(W,U);return}default:throw new Z(`Unknown personalization command "${u}". Try: get, set`,Q.USAGE)}}import{existsSync as Y$}from"node:fs";import{mkdir as BU,writeFile as PU,readFile as dU}from"node:fs/promises";import{dirname as l,join as R}from"node:path";import{homedir as a}from"node:os";import{fileURLToPath as q$}from"node:url";var p=[{id:"claude",label:"Claude Code",aliases:["claude-code"],dest:(U)=>R(U,".claude","skills","dunsocial","SKILL.md")},{id:"codex",label:"Codex",dest:(U)=>R(U,".codex","skills","dunsocial","SKILL.md"),afterInstall:async(U)=>{return{agentsPath:await G$(U)}}},{id:"cursor",label:"Cursor",dest:(U)=>R(U,".cursor","skills","dunsocial","SKILL.md")},{id:"pi",label:"Pi",dest:(U)=>R(U,".pi","agent","skills","dunsocial","SKILL.md")},{id:"opencode",label:"OpenCode",dest:(U)=>R(U,".config","opencode","skills","dunsocial","SKILL.md")},{id:"agents",label:"Universal (.agents)",aliases:["universal"],dest:(U)=>R(U,".agents","skills","dunsocial","SKILL.md")},{id:"copilot",label:"GitHub Copilot",aliases:["github-copilot","github"],dest:(U)=>R(U,".copilot","skills","dunsocial","SKILL.md")},{id:"windsurf",label:"Windsurf",dest:(U)=>R(U,".codeium","windsurf","skills","dunsocial","SKILL.md")},{id:"goose",label:"Goose",dest:(U)=>R(U,".config","goose","skills","dunsocial","SKILL.md")},{id:"gemini",label:"Gemini CLI",aliases:["gemini-cli"],dest:(U)=>R(U,".gemini","skills","dunsocial","SKILL.md")},{id:"amp",label:"Amp",dest:(U)=>R(U,".config","agents","skills","dunsocial","SKILL.md")},{id:"grok",label:"Grok Build",dest:(U)=>R(U,".grok","skills","dunsocial","SKILL.md")},{id:"continue",label:"Continue",dest:(U)=>R(U,".continue","skills","dunsocial","SKILL.md")},{id:"antigravity",label:"Antigravity",dest:(U)=>R(U,".gemini","antigravity","skills","dunsocial","SKILL.md")}],r=new Map;for(let U of p){r.set(U.id,U);for(let u of U.aliases??[])r.set(u,U)}var HU=`Usage: dun skill install <${p.map((U)=>U.id).join("|")}|all>`;function s(){let U=l(q$(import.meta.url)),u=R("skills","dunsocial","SKILL.md"),$=[R(U,"..",u),R(U,"..","..",u)];return $.find(Y$)??$[0]}function K$(){let U=a();return p.map((u)=>({id:u.id,label:u.label,path:u.dest(U),aliases:u.aliases??[]}))}async function y$(){try{return await dU(s(),"utf8")}catch{throw new Z(`Bundled skill not found at ${s()}. Reinstall dunsocial.`,Q.USAGE)}}async function W$(U,u){await BU(l(U),{recursive:!0}),await PU(U,u,"utf8")}async function G$(U){let u=R(a(),".codex","AGENTS.md");await BU(l(u),{recursive:!0});let $="";try{$=await dU(u,"utf8")}catch{$=""}let z=["<!-- BEGIN DUNSOCIAL CLI SKILL -->","## DunSocial CLI","","Use the `dun` CLI for DunSocial scheduling from the shell. Prefer `--json` output.",`Skill details: ${U}`,"","Common flow:","1. `dun auth status --json`","2. `dun workspace list --json` / `dun workspace use <id>`","3. `dun accounts list --json`",'4. `dun posts schedule --text "..." --accounts <id> --in 1h --json`',"<!-- END DUNSOCIAL CLI SKILL -->"].join(`
|
|
170
|
-
`),
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
215
|
+
`)}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()}
|
|
220
|
+
|
|
221
|
+
${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}
|
|
176
225
|
Run: dun update
|
|
177
|
-
Or: ${
|
|
178
|
-
Cannot auto-update this install (${
|
|
179
|
-
Run: ${
|
|
180
|
-
`);let
|
|
181
|
-
|
|
182
|
-
`);process.stdout.write(`${
|
|
183
|
-
`)}async function
|
|
184
|
-
`)}async function
|
|
185
|
-
`),
|
|
186
|
-
`);else
|
|
187
|
-
`),
|
|
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()}
|
|
230
|
+
|
|
231
|
+
`);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);
|
package/package.json
CHANGED
|
@@ -69,6 +69,13 @@ Absolute time: `--at 2026-06-01T09:00:00Z`.
|
|
|
69
69
|
dun posts publish --text "Hello from dun" --accounts acc_x --json
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
`publish` **queues** work (`status: scheduled`). For a terminal outcome use `--wait`:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
dun posts publish --text "Hello" --accounts acc_x --wait --json
|
|
76
|
+
# optional: --wait-timeout 180
|
|
77
|
+
```
|
|
78
|
+
|
|
72
79
|
## Upload media then post
|
|
73
80
|
|
|
74
81
|
```bash
|
|
@@ -76,6 +83,63 @@ dun media upload ./shot.png --alt "Product screenshot" --json
|
|
|
76
83
|
dun posts schedule --text "…" --accounts acc_x --media <assetId> --in 30m --json
|
|
77
84
|
```
|
|
78
85
|
|
|
86
|
+
Local preflight rejects images > 8MB and video > 256MB **before** upload-url. Compress video first if needed:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
ffmpeg -i input.mp4 -vf scale=-2:1080 -c:v libx264 -crf 23 -c:a aac output.mp4
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Reddit (special)
|
|
93
|
+
|
|
94
|
+
Reddit needs platform metadata under `--meta`. Discover flairs first — many subs require a **template id**, not free text.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
dun accounts list --platform reddit --json
|
|
98
|
+
dun reddit flairs tamilyapping --json
|
|
99
|
+
# -> { id, text, textEditable }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Required `metadata.reddit` fields:
|
|
103
|
+
|
|
104
|
+
| Field | Notes |
|
|
105
|
+
|-------|--------|
|
|
106
|
+
| `subreddit` | without `r/` |
|
|
107
|
+
| `kind` | `self` \| `link` \| `image` \| `video` |
|
|
108
|
+
| `title` | max 300 (sub may be stricter) |
|
|
109
|
+
| `flairId` | template id from `dun reddit flairs` when flair is required |
|
|
110
|
+
|
|
111
|
+
Optional: `body` (self), `url` (link), `flairText` (**only** when `textEditable` is true), `posterMediaAssetId`, `nsfw`, `spoiler`.
|
|
112
|
+
|
|
113
|
+
**Do not** send `flairText` without `flairId`. For non-editable templates, send **`flairId` alone**.
|
|
114
|
+
|
|
115
|
+
### Worked Reddit video example
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
dun media upload ./demo.mp4 --json
|
|
119
|
+
# assetId from response
|
|
120
|
+
|
|
121
|
+
dun reddit flairs tamilyapping --json
|
|
122
|
+
# pick flairId
|
|
123
|
+
|
|
124
|
+
dun posts validate \
|
|
125
|
+
--accounts <redditAccountId> \
|
|
126
|
+
--media <assetId> \
|
|
127
|
+
--meta '{"reddit":{"subreddit":"tamilyapping","kind":"video","title":"Approved title","flairId":"<id>"}}' \
|
|
128
|
+
--json
|
|
129
|
+
|
|
130
|
+
dun posts publish \
|
|
131
|
+
--accounts <redditAccountId> \
|
|
132
|
+
--media <assetId> \
|
|
133
|
+
--meta '{"reddit":{"subreddit":"tamilyapping","kind":"video","title":"Approved title","flairId":"<id>"}}' \
|
|
134
|
+
--wait \
|
|
135
|
+
--json
|
|
136
|
+
# (--text optional for Reddit when metadata.reddit.title is set)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`--wait` polls until `published` or `failed` and returns `publishedPostUrl` (post permalink, not the subreddit root).
|
|
140
|
+
|
|
141
|
+
Also: `dun reddit requirements <subreddit> --json` for live rules (`is_flair_required`, title limits).
|
|
142
|
+
|
|
79
143
|
## Drafts / memory / threads
|
|
80
144
|
|
|
81
145
|
```bash
|
|
@@ -84,7 +148,7 @@ dun memory collections list --json
|
|
|
84
148
|
dun memory save --collection <id> --text "…" --json
|
|
85
149
|
dun memory search --prompt "…" --collections <id> --json
|
|
86
150
|
|
|
87
|
-
# thread.json: [ {"content":"1"},
|
|
151
|
+
# thread.json: [ {"content":"1"},{"content":"2"} ]
|
|
88
152
|
dun posts schedule-thread --account acc_x --file ./thread.json --in 2h --json
|
|
89
153
|
```
|
|
90
154
|
|
|
@@ -132,7 +196,8 @@ Failure JSON:
|
|
|
132
196
|
- Need workspace: `workspace use` or `--workspace` / `DUN_WORKSPACE_ID`
|
|
133
197
|
- `--accounts` = DunSocial social account **ids**, not handles
|
|
134
198
|
- Upload local files with `media upload` before attaching
|
|
135
|
-
-
|
|
199
|
+
- Reddit (and some other platforms) need `--meta '{"reddit":{…}}'`
|
|
200
|
+
- `posts get` / list never include OAuth secrets — safe to log
|
|
136
201
|
- MCP OAuth and CLI tokens are different systems
|
|
137
202
|
|
|
138
203
|
## Help
|
|
@@ -140,4 +205,5 @@ Failure JSON:
|
|
|
140
205
|
```bash
|
|
141
206
|
dun --help
|
|
142
207
|
dun posts --help
|
|
208
|
+
dun reddit --help
|
|
143
209
|
```
|