dunsocial 0.2.0 → 0.3.2
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 +56 -111
- package/dist/index.js +13 -13
- package/package.json +3 -3
- package/skills/dunsocial/SKILL.md +42 -49
package/README.md
CHANGED
|
@@ -1,157 +1,102 @@
|
|
|
1
|
-
# DunSocial CLI
|
|
1
|
+
# DunSocial CLI
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Wraps the existing Mobile API (`apps/api`) — **no duplicate publish logic**.
|
|
6
|
-
Remote AI hosts (ChatGPT / Claude web) should keep using **MCP**. This CLI is for local agents (Claude Code, Codex), CI, and power users.
|
|
7
|
-
|
|
8
|
-
Linear: [DUN-262](https://linear.app/thisuxco/issue/DUN-262) · [DUN-263](https://linear.app/thisuxco/issue/DUN-263) · [DUN-264](https://linear.app/thisuxco/issue/DUN-264)
|
|
3
|
+
Post and schedule to your social accounts from the terminal.
|
|
9
4
|
|
|
10
5
|
## Install
|
|
11
6
|
|
|
12
|
-
### Monorepo (dev)
|
|
13
|
-
|
|
14
7
|
```bash
|
|
15
|
-
|
|
16
|
-
bun run dev:cli -- --help
|
|
17
|
-
bun apps/cli/src/index.ts auth status --json
|
|
8
|
+
npm install -g dunsocial
|
|
18
9
|
```
|
|
19
10
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
bun run --cwd apps/cli build
|
|
24
|
-
node apps/cli/dist/index.js --help
|
|
11
|
+
Then use the `dun` command (Node.js 20+).
|
|
25
12
|
|
|
26
|
-
|
|
27
|
-
npm i -g dunsocial
|
|
28
|
-
dun --help
|
|
29
|
-
```
|
|
13
|
+
---
|
|
30
14
|
|
|
31
|
-
|
|
15
|
+
## Get started
|
|
32
16
|
|
|
33
|
-
|
|
17
|
+
### 1. Sign in
|
|
34
18
|
|
|
35
19
|
```bash
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
export DUN_API_URL=https://api.dunsocial.com # optional
|
|
39
|
-
|
|
40
|
-
# 2. Pick a workspace
|
|
41
|
-
dun workspace list --json
|
|
42
|
-
dun workspace use <id-or-slug>
|
|
20
|
+
dun auth login
|
|
21
|
+
```
|
|
43
22
|
|
|
44
|
-
|
|
45
|
-
dun accounts list --json
|
|
23
|
+
A browser window opens so you can approve access. After that, you’re set on this machineأتى### 2. Choose your workspace
|
|
46
24
|
|
|
47
|
-
|
|
48
|
-
dun
|
|
49
|
-
|
|
50
|
-
--accounts <socialAccountId> \
|
|
51
|
-
--in 1h \
|
|
52
|
-
--json
|
|
25
|
+
```bash
|
|
26
|
+
dun workspace list
|
|
27
|
+
dun workspace use your-workspace
|
|
53
28
|
```
|
|
54
29
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
### Device login (default)
|
|
30
|
+
### 3. See connected accounts
|
|
58
31
|
|
|
59
32
|
```bash
|
|
60
|
-
dun
|
|
61
|
-
# opens app.dunsocial.com/cli/authorize — approve, CLI stores token
|
|
62
|
-
dun auth login --no-browser # print URL only
|
|
33
|
+
dun accounts list
|
|
63
34
|
```
|
|
64
35
|
|
|
65
|
-
|
|
36
|
+
Copy the **account id** you want to post with (not the @username).
|
|
66
37
|
|
|
67
|
-
|
|
38
|
+
### 4. Schedule a post
|
|
68
39
|
|
|
69
40
|
```bash
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
41
|
+
dun posts schedule \
|
|
42
|
+
--text "Hello from DunSocial" \
|
|
43
|
+
--accounts ACCOUNT_ID \
|
|
44
|
+
--in 1h
|
|
73
45
|
```
|
|
74
46
|
|
|
75
|
-
|
|
47
|
+
Post right away:
|
|
76
48
|
|
|
77
49
|
```bash
|
|
78
|
-
dun
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
dun auth logout
|
|
50
|
+
dun posts publish \
|
|
51
|
+
--text "Hello from DunSocial" \
|
|
52
|
+
--accounts ACCOUNT_ID
|
|
82
53
|
```
|
|
83
54
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
Env (wins over config):
|
|
55
|
+
---
|
|
87
56
|
|
|
88
|
-
|
|
89
|
-
|-----|---------|
|
|
90
|
-
| `DUN_TOKEN` | Bearer token |
|
|
91
|
-
| `DUN_WORKSPACE_ID` | Default workspace |
|
|
92
|
-
| `DUN_API_URL` | API base URL |
|
|
93
|
-
| `DUN_JSON=1` | Force JSON output |
|
|
94
|
-
| `DUN_DEBUG=1` | Log HTTP to stderr |
|
|
57
|
+
## Automated posting (CI)
|
|
95
58
|
|
|
96
|
-
|
|
59
|
+
1. In DunSocial, open **Settings → CLI**
|
|
60
|
+
2. Create an access token and copy it (shown once)
|
|
61
|
+
3. Add these secrets in your CI:
|
|
62
|
+
- `DUN_TOKEN` — your access token
|
|
63
|
+
- `DUN_WORKSPACE_ID` — your workspace id
|
|
97
64
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
| Area | Examples |
|
|
103
|
-
|------|----------|
|
|
104
|
-
| Workspaces | `workspace list\|use\|current` |
|
|
105
|
-
| Accounts | `accounts list [--platform x]` |
|
|
106
|
-
| Posts | `schedule`, `publish`, `list`, `get`, `reschedule`, `cancel`, `delete`, `x-cap`, `schedule-thread`, `publish-thread` |
|
|
107
|
-
| Drafts | `create`, `list`, `get`, `update`, `delete` |
|
|
108
|
-
| Media | `upload <file>`, `list`, `get`, `delete` |
|
|
109
|
-
| Memory | `collections list\|create`, `save`, `list`, `search`, `delete` |
|
|
110
|
-
| Skills | `skill install claude\|codex` |
|
|
111
|
-
|
|
112
|
-
### Global flags
|
|
65
|
+
```bash
|
|
66
|
+
export DUN_TOKEN=your_token
|
|
67
|
+
export DUN_WORKSPACE_ID=your_workspace_id
|
|
113
68
|
|
|
69
|
+
dun posts schedule \
|
|
70
|
+
--text "We just shipped a new release" \
|
|
71
|
+
--accounts ACCOUNT_ID \
|
|
72
|
+
--in 5m
|
|
114
73
|
```
|
|
115
|
-
--json --workspace <id> --api-url <url> --token <token>
|
|
116
|
-
--debug --quiet --yes --help --version
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
JSON is the default when stdout is not a TTY or `CI=1`.
|
|
120
|
-
|
|
121
|
-
### Exit codes
|
|
122
74
|
|
|
123
|
-
|
|
75
|
+
---
|
|
124
76
|
|
|
125
|
-
##
|
|
77
|
+
## Useful commands
|
|
126
78
|
|
|
127
79
|
```bash
|
|
128
|
-
dun
|
|
129
|
-
dun
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
bun run --cwd apps/cli test
|
|
136
|
-
bun run --cwd apps/cli type-check
|
|
137
|
-
bun run --cwd apps/cli build
|
|
138
|
-
bun run --cwd apps/cli release-check
|
|
80
|
+
dun --help
|
|
81
|
+
dun auth status
|
|
82
|
+
dun posts list
|
|
83
|
+
dun media upload ./photo.png
|
|
84
|
+
dun drafts create --text "Idea for later"
|
|
139
85
|
```
|
|
140
86
|
|
|
141
|
-
|
|
87
|
+
Tips:
|
|
142
88
|
|
|
143
|
-
|
|
89
|
+
- Help for any command: `dun posts --help`
|
|
90
|
+
- Structured output for scripts: add `--json`
|
|
91
|
+
- Prefer ChatGPT or Claude **in the browser**? Use [MCP](https://docs.dunsocial.com/mcp) instead of the CLI
|
|
144
92
|
|
|
145
|
-
|
|
146
|
-
2. Merge the **release-please** PR (version + `CHANGELOG.md`)
|
|
147
|
-
3. CI publishes `dunsocial` to npm (`NPM_TOKEN` secret)
|
|
93
|
+
---
|
|
148
94
|
|
|
149
|
-
|
|
95
|
+
## Links
|
|
150
96
|
|
|
151
|
-
|
|
97
|
+
- [dunsocial.com](https://dunsocial.com)
|
|
98
|
+
- [npm package](https://www.npmjs.com/package/dunsocial)
|
|
152
99
|
|
|
153
|
-
|
|
154
|
-
dun → HTTPS + Bearer + X-Workspace-Id → apps/api REST
|
|
155
|
-
```
|
|
100
|
+
## License
|
|
156
101
|
|
|
157
|
-
|
|
102
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
-
import{mkdir as
|
|
3
|
+
import{mkdir as rn,readFile as Ie,writeFile as tn,chmod as mn,unlink as on}from"node:fs/promises";import{dirname as wn,join as ue}from"node:path";import{homedir as pe}from"node:os";var o={OK:0,USAGE:1,AUTH:2,FORBIDDEN:3,VALIDATION:4,NOT_FOUND:5,RATE_LIMITED:6,NETWORK:7};class w extends Error{code;details;constructor(e,n=o.USAGE,u){super(e);this.name="CliError",this.code=n,this.details=u}}function ne(e){if(e===401)return o.AUTH;if(e===403)return o.FORBIDDEN;if(e===404)return o.NOT_FOUND;if(e===429)return o.RATE_LIMITED;if(e>=400&&e<500)return o.VALIDATION;return o.NETWORK}var Ge="https://api.dunsocial.com",M="0.3.1";function Ve(){if(process.env.DUN_CONFIG)return process.env.DUN_CONFIG;let e=process.env.XDG_CONFIG_HOME,n=e?e:ue(pe(),".config");return ue(n,"dunsocial","config.json")}async function _e(){let e=Ve();try{let n=await Ie(e,"utf8");return JSON.parse(n)}catch(n){if(n.code==="ENOENT")return{};throw new w(`Failed to read config at ${e}: ${n.message}`,o.USAGE)}}async function de(e){let n=await _e(),u=(e.apiUrl||process.env.DUN_API_URL||n.apiUrl||Ge).replace(/\/$/,""),t=e.token||process.env.DUN_TOKEN||n.token,y=e.workspace||process.env.DUN_WORKSPACE_ID||n.defaultWorkspaceId||void 0,d=process.env.DUN_JSON==="0",i=!process.stdout.isTTY,r=d?!1:e.json===!0||process.env.DUN_JSON==="1"||process.env.CI==="true"||process.env.CI==="1"||i;return{apiUrl:u,token:t,workspaceId:y,json:r,debug:e.debug===!0||process.env.DUN_DEBUG==="1",quiet:e.quiet===!0,yes:e.yes===!0||process.env.CI==="true"||process.env.CI==="1",config:n}}function re(e){let n={},u=[],t=0,y=!0;while(t<e.length){let O=e[t];if(y&&O==="--"){y=!1,t+=1;continue}if(y&&O.startsWith("--")){let A=O.indexOf("=");if(A!==-1){n[O.slice(2,A)]=O.slice(A+1),t+=1;continue}let T=O.slice(2),U=e[t+1];if(U!==void 0&&!U.startsWith("-"))n[T]=U,t+=2;else n[T]=!0,t+=1;continue}if(y&&O.startsWith("-")&&O.length>1&&O!=="-"){let A=O.slice(1);if(A.length>1&&!A.includes("=")){for(let V of A)n[V]=!0;t+=1;continue}let T=A,U=e[t+1];if(U!==void 0&&!U.startsWith("-"))n[T]=U,t+=2;else n[T]=!0,t+=1;continue}u.push(O),t+=1}let d=u[0],i,r,m=1;if(d==="memory"&&(u[1]==="collections"||u[1]==="collection"))r="collections",i=u[2],m=i?3:2;else i=u[1],m=i?2:1;return{group:d,action:i,nest:r,flags:n,positionals:u.slice(m)}}function Q(e,...n){for(let u of n){let t=e[u];if(typeof t==="string"&&t.length>0)return t}return}function K(e,...n){for(let u of n){let t=e[u];if(t===!0)return!0;if(typeof t==="string"){let y=t.toLowerCase();if(y==="1"||y==="true"||y==="yes")return!0}}return!1}function q(e){return K(e,"help","h")}function h(e){return K(e,"version","v","V")}function s(e,n){if(n.quiet&&!n.json)return;if(n.json||!process.stdout.isTTY){process.stdout.write(`${JSON.stringify({ok:!0,data:e},null,2)}
|
|
4
4
|
`);return}if(e===void 0||e===null){process.stdout.write(`ok
|
|
5
5
|
`);return}if(typeof e==="string"){process.stdout.write(`${e}
|
|
6
6
|
`);return}process.stdout.write(`${Je(e)}
|
|
@@ -12,8 +12,8 @@ import{mkdir as dn,readFile as Ie,writeFile as rn,chmod as tn,unlink as mn}from"
|
|
|
12
12
|
${t}`:t}return Object.entries(n).map(([u,t])=>`${u}: ${P(t)}`).join(`
|
|
13
13
|
`)}return String(e)}function te(e){if(e.length===0)return"(empty)";let n=["id","name","slug","status","providerName","username","displayName","role","scheduledAt","content","text","originalFilename","score","collectionId"],u=new Set;for(let m of e)for(let O of Object.keys(m))u.add(O);let t=[...n.filter((m)=>u.has(m)),...[...u].filter((m)=>!n.includes(m)).slice(0,6)].slice(0,8),y=e.map((m)=>t.map((O)=>He(P(m[O]),O==="content"||O==="text"?48:28))),d=t.map((m,O)=>Math.max(m.length,...y.map((A)=>A[O]?.length??0))),i=t.map((m,O)=>m.padEnd(d[O])).join(" "),r=y.map((m)=>m.map((O,A)=>O.padEnd(d[A])).join(" ")).join(`
|
|
14
14
|
`);return`${i}
|
|
15
|
-
${r}`}function P(e){if(e===null||e===void 0)return"";if(typeof e==="string")return e.replace(/\s+/g," ").trim();if(typeof e==="number"||typeof e==="boolean")return String(e);if(e instanceof Date)return e.toISOString();if(typeof e==="object"){let n=e;if(typeof n.id==="string"&&typeof n.name==="string")return`${n.name} (${n.id})`;if(typeof n.status==="string")return n.status;return JSON.stringify(e)}return String(e)}function He(e,n){if(e.length<=n)return e;return`${e.slice(0,Math.max(0,n-1))}…`}var G={OK:0,USAGE:1,AUTH:2,FORBIDDEN:3,VALIDATION:4,NOT_FOUND:5,RATE_LIMITED:6,NETWORK:7};class S extends Error{code;details;constructor(e,n=G.USAGE,u){super(e);this.name="CliError",this.code=n,this.details=u}}import{mkdir as Ye,readFile as
|
|
16
|
-
`,"utf8");try{await We(n,384)}catch{}}async function oe(){let e=E();try{await Me(e)}catch(n){if(n.code!=="ENOENT")throw new w(`Failed to clear config: ${n.message}`,o.USAGE)}}function R(e){if(!e.token)throw new w("Not authenticated. Run `dun auth login --token <token>` or set DUN_TOKEN.",o.AUTH);return e.token}function k(e){if(!e.workspaceId)throw new w("No workspace selected. Run `dun workspace use <id|slug>` or pass --workspace / DUN_WORKSPACE_ID.",o.VALIDATION);return e.workspaceId}var
|
|
15
|
+
${r}`}function P(e){if(e===null||e===void 0)return"";if(typeof e==="string")return e.replace(/\s+/g," ").trim();if(typeof e==="number"||typeof e==="boolean")return String(e);if(e instanceof Date)return e.toISOString();if(typeof e==="object"){let n=e;if(typeof n.id==="string"&&typeof n.name==="string")return`${n.name} (${n.id})`;if(typeof n.status==="string")return n.status;return JSON.stringify(e)}return String(e)}function He(e,n){if(e.length<=n)return e;return`${e.slice(0,Math.max(0,n-1))}…`}var G={OK:0,USAGE:1,AUTH:2,FORBIDDEN:3,VALIDATION:4,NOT_FOUND:5,RATE_LIMITED:6,NETWORK:7};class S extends Error{code;details;constructor(e,n=G.USAGE,u){super(e);this.name="CliError",this.code=n,this.details=u}}import{mkdir as Ye,readFile as bn,writeFile as ze,chmod as We,unlink as Me}from"node:fs/promises";import{dirname as Qe,join as me}from"node:path";import{homedir as Xe}from"node:os";var X="0.3.1";function E(){if(process.env.DUN_CONFIG)return process.env.DUN_CONFIG;let e=process.env.XDG_CONFIG_HOME,n=e?e:me(Xe(),".config");return me(n,"dunsocial","config.json")}function J(){return E()}async function H(e){let n=E();await Ye(Qe(n),{recursive:!0});let u={...e,updatedAt:new Date().toISOString()};await ze(n,`${JSON.stringify(u,null,2)}
|
|
16
|
+
`,"utf8");try{await We(n,384)}catch{}}async function oe(){let e=E();try{await Me(e)}catch(n){if(n.code!=="ENOENT")throw new w(`Failed to clear config: ${n.message}`,o.USAGE)}}function R(e){if(!e.token)throw new w("Not authenticated. Run `dun auth login --token <token>` or set DUN_TOKEN.",o.AUTH);return e.token}function k(e){if(!e.workspaceId)throw new w("No workspace selected. Run `dun workspace use <id|slug>` or pass --workspace / DUN_WORKSPACE_ID.",o.VALIDATION);return e.workspaceId}var Z=`dun — DunSocial agent-first CLI v${X}
|
|
17
17
|
|
|
18
18
|
Usage:
|
|
19
19
|
dun <command> [flags]
|
|
@@ -111,7 +111,7 @@ Docs: https://dunsocial.com · MCP remains the remote-host path; CLI is local/CI
|
|
|
111
111
|
x-cap
|
|
112
112
|
`,drafts:"dun drafts list|get|create|update|delete",media:"dun media list|get|upload|delete",memory:`dun memory collections list|create
|
|
113
113
|
dun memory list|save|search|delete`,skill:`dun skill install claude|codex
|
|
114
|
-
dun skill path`}[e]??
|
|
114
|
+
dun skill path`}[e]??Z}function N(e,n){if(n.quiet&&!n.json)return;if(n.json||!process.stdout.isTTY){process.stdout.write(`${JSON.stringify({ok:!0,data:e},null,2)}
|
|
115
115
|
`);return}if(e===void 0||e===null){process.stdout.write(`ok
|
|
116
116
|
`);return}if(typeof e==="string"){process.stdout.write(`${e}
|
|
117
117
|
`);return}process.stdout.write(`${Ze(e)}
|
|
@@ -120,9 +120,9 @@ dun skill path`}[e]??X}function N(e,n){if(n.quiet&&!n.json)return;if(n.json||!pr
|
|
|
120
120
|
${t}`:t}return Object.entries(n).map(([u,t])=>`${u}: ${v(t)}`).join(`
|
|
121
121
|
`)}return String(e)}function ye(e){if(e.length===0)return"(empty)";let n=["id","name","slug","status","providerName","username","displayName","role","scheduledAt","content","text","originalFilename","score","collectionId"],u=new Set;for(let m of e)for(let O of Object.keys(m))u.add(O);let t=[...n.filter((m)=>u.has(m)),...[...u].filter((m)=>!n.includes(m)).slice(0,6)].slice(0,8),y=e.map((m)=>t.map((O)=>Be(v(m[O]),O==="content"||O==="text"?48:28))),d=t.map((m,O)=>Math.max(m.length,...y.map((A)=>A[O]?.length??0))),i=t.map((m,O)=>m.padEnd(d[O])).join(" "),r=y.map((m)=>m.map((O,A)=>O.padEnd(d[A])).join(" ")).join(`
|
|
122
122
|
`);return`${i}
|
|
123
|
-
${r}`}function v(e){if(e===null||e===void 0)return"";if(typeof e==="string")return e.replace(/\s+/g," ").trim();if(typeof e==="number"||typeof e==="boolean")return String(e);if(e instanceof Date)return e.toISOString();if(typeof e==="object"){let n=e;if(typeof n.id==="string"&&typeof n.name==="string")return`${n.name} (${n.id})`;if(typeof n.status==="string")return n.status;return JSON.stringify(e)}return String(e)}function Be(e,n){if(e.length<=n)return e;return`${e.slice(0,Math.max(0,n-1))}…`}function
|
|
123
|
+
${r}`}function v(e){if(e===null||e===void 0)return"";if(typeof e==="string")return e.replace(/\s+/g," ").trim();if(typeof e==="number"||typeof e==="boolean")return String(e);if(e instanceof Date)return e.toISOString();if(typeof e==="object"){let n=e;if(typeof n.id==="string"&&typeof n.name==="string")return`${n.name} (${n.id})`;if(typeof n.status==="string")return n.status;return JSON.stringify(e)}return String(e)}function Be(e,n){if(e.length<=n)return e;return`${e.slice(0,Math.max(0,n-1))}…`}function Y(e,n,u){if(!e)return;if(u!==void 0){process.stderr.write(`[debug] ${n} ${JSON.stringify(u)}
|
|
124
124
|
`);return}process.stderr.write(`[debug] ${n}
|
|
125
|
-
`)}class we{ctx;constructor(e){this.ctx=e}async request(e){let n=e.method??"GET",u=new URL(e.path.startsWith("http")?e.path:`${this.ctx.apiUrl}${e.path}`);if(e.query)for(let[m,O]of Object.entries(e.query)){if(O===void 0||O===null||O==="")continue;u.searchParams.set(m,String(O))}let t={Accept:"application/json","User-Agent":`dunsocial-cli/${
|
|
125
|
+
`)}class we{ctx;constructor(e){this.ctx=e}async request(e){let n=e.method??"GET",u=new URL(e.path.startsWith("http")?e.path:`${this.ctx.apiUrl}${e.path}`);if(e.query)for(let[m,O]of Object.entries(e.query)){if(O===void 0||O===null||O==="")continue;u.searchParams.set(m,String(O))}let t={Accept:"application/json","User-Agent":`dunsocial-cli/${X}`,...e.headers??{}};if(e.auth!==!1)t.Authorization=`Bearer ${R(this.ctx)}`;if(e.workspace)t["X-Workspace-Id"]=k(this.ctx);let y;if(e.body!==void 0)t["Content-Type"]="application/json",y=JSON.stringify(e.body);Y(this.ctx.debug,`${n} ${u.toString()}`);let d;try{d=await fetch(u,{method:n,headers:t,body:y})}catch(m){throw new w(`Network error: ${m.message}`,o.NETWORK,{cause:String(m)})}let i=await d.text(),r=null;if(i)try{r=JSON.parse(i)}catch{r=null}if(Y(this.ctx.debug,`← ${d.status}`,{ok:d.ok,bodyPreview:i.slice(0,300)}),!d.ok){let m=r?.error||r?.message||(i?i.slice(0,300):`HTTP ${d.status}`);throw new w(m,ne(d.status),{status:d.status,body:r??i})}if(r&&typeof r==="object"&&"success"in r){if(r.success===!1)throw new w(r.error||r.message||"Request failed",o.VALIDATION,r);return r.data}return r??void 0}get(e,n){return this.request({...n,method:"GET",path:e})}post(e,n,u){return this.request({...u,method:"POST",path:e,body:n})}patch(e,n,u){return this.request({...u,method:"PATCH",path:e,body:n})}delete(e,n,u){return this.request({...u,method:"DELETE",path:e,body:n})}}function D(e){return new we(e)}function $(e,...n){for(let u of n){let t=e[u];if(typeof t==="string"&&t.length>0)return t}return}function j(e,...n){for(let u of n){let t=e[u];if(t===!0)return!0;if(typeof t==="string"){let y=t.toLowerCase();if(y==="1"||y==="true"||y==="yes")return!0}}return!1}function I(e,...n){let u=$(e,...n);if(u===void 0)return;let t=Number(u);if(!Number.isFinite(t))throw Error(`Invalid number for --${n[0]}: ${u}`);return t}function p(e,...n){let u=$(e,...n);if(u===void 0)return;return u.split(",").map((t)=>t.trim()).filter(Boolean)}async function Oe(e,n,u,t){switch(n){case"login":return Fe(e,u);case"logout":return Pe(e);case"whoami":return se(e);case"status":return Se(e);default:throw new w(`Unknown auth command "${n??""}". Try: login, logout, whoami, status`,o.USAGE)}}async function Fe(e,n){let u=$(n,"token")||process.env.DUN_TOKEN;if(u)return qe(e,u);return he(e,n)}async function qe(e,n){let u={...e,token:n},y=await D(u).get("/api/user/profile");await H({...e.config,apiUrl:e.apiUrl,token:n,defaultWorkspaceId:e.config.defaultWorkspaceId,defaultWorkspaceSlug:e.config.defaultWorkspaceSlug,defaultWorkspaceName:e.config.defaultWorkspaceName}),N({user:{id:y.id,name:y.name,email:y.email},configPath:J(),method:"token",message:"Logged in. Next: dun workspace list && dun workspace use <id|slug>"},e)}async function he(e,n){let u=D({...e,token:void 0}),t=await u.post("/api/cli/auth/start",{clientName:"dun-cli"},{auth:!1}),y=!j(n,"no-browser");if(!e.json)process.stderr.write(`
|
|
126
126
|
DunSocial CLI login
|
|
127
127
|
`),process.stderr.write(`───────────────────
|
|
128
128
|
`),process.stderr.write(`In your browser, open:
|
|
@@ -133,14 +133,14 @@ DunSocial CLI login
|
|
|
133
133
|
|
|
134
134
|
`),process.stderr.write(`Waiting for approval…
|
|
135
135
|
`);else process.stderr.write(JSON.stringify({ok:!0,phase:"waiting",userCode:t.userCode,verificationUrlComplete:t.verificationUrlComplete})+`
|
|
136
|
-
`);if(y&&process.stdout.isTTY)try{await fe(t.verificationUrlComplete)}catch{}let d=Math.max(3,t.interval||5)*1000,i=Date.now()+(t.expiresIn||900)*1000;while(Date.now()<i){await Ee(d);let r=await u.post("/api/cli/auth/poll",{deviceCode:t.deviceCode},{auth:!1});if(r.status==="pending")continue;if(r.status==="expired")throw new w("Login expired. Run dun auth login again.",o.AUTH);if(r.status==="denied")throw new w("Login was denied in the browser.",o.AUTH);if(r.status==="approved"){let m=r.token,O={...e,token:m},A=D(O),T=await A.get("/api/user/profile"),U={...e.config,apiUrl:e.apiUrl,token:m,defaultWorkspaceId:r.workspaceId||e.config.defaultWorkspaceId,defaultWorkspaceSlug:e.config.defaultWorkspaceSlug,defaultWorkspaceName:e.config.defaultWorkspaceName};if(r.workspaceId)try{let b=(await A.get("/api/workspaces")).find((_)=>_.id===r.workspaceId);if(b)U.defaultWorkspaceId=b.id,U.defaultWorkspaceSlug=b.slug,U.defaultWorkspaceName=b.name}catch{}await J(U),N({user:{id:T.id,name:T.name,email:T.email},workspaceId:U.defaultWorkspaceId??null,configPath:L(),method:"device",message:U.defaultWorkspaceId?"Logged in.":"Logged in. Next: dun workspace list && dun workspace use <id|slug>"},e);return}}throw new w("Login timed out. Run dun auth login again.",o.AUTH)}async function Pe(e){await oe(),N({message:"Logged out. Config cleared.",configPath:L()},e)}async function se(e){R(e);let u=await D(e).get("/api/user/profile");N({id:u.id,name:u.name,email:u.email,image:u.image??null,apiUrl:e.apiUrl,workspaceId:e.workspaceId??null},e)}async function Se(e){let n=Boolean(e.token),u=null,t=null;if(n)try{u=await D(e).get("/api/user/profile")}catch(y){t=y instanceof Error?y.message:String(y)}N({authenticated:Boolean(u),tokenPresent:n,apiUrl:e.apiUrl,workspaceId:e.workspaceId??null,workspaceSlug:e.config.defaultWorkspaceSlug??null,workspaceName:e.config.defaultWorkspaceName??null,configPath:L(),user:u?{id:u.id,name:u.name,email:u.email}:null,error:t},e)}function Ee(e){return new Promise((n)=>setTimeout(n,e))}async function fe(e){let n=process.platform,u=n==="darwin"?["open",e]:n==="win32"?["cmd","/c","start","",e]:["xdg-open",e];await Bun.spawn(u,{stdout:"ignore",stderr:"ignore"}).exited}async function Ne(e,n,u,t){R(e);let y=D(e);switch(n){case"list":case"ls":{let d=await y.get("/api/workspaces");N(d.map((i)=>({id:i.id,name:i.name,slug:i.slug,role:i.role,timezone:i.timezone,plan:i.subscription?.planName??null,status:i.subscription?.status??null})),e);return}case"use":{let d=t[0];if(!d)throw new w("Usage: dun workspace use <id|slug>",o.USAGE);let r=(await y.get("/api/workspaces")).find((m)=>m.id===d||m.slug===d);if(!r)throw new w(`Workspace not found: ${d}. Run dun workspace list`,o.NOT_FOUND);await J({...e.config,apiUrl:e.apiUrl,token:e.token,defaultWorkspaceId:r.id,defaultWorkspaceSlug:r.slug,defaultWorkspaceName:r.name}),N({id:r.id,name:r.name,slug:r.slug,role:r.role,message:`Default workspace set to ${r.name}`},e);return}case"current":{if(!e.workspaceId&&!e.config.defaultWorkspaceId)throw new w("No workspace selected. Run dun workspace use <id|slug>",o.VALIDATION);let d=e.workspaceId||e.config.defaultWorkspaceId;try{let r=(await y.get("/api/workspaces")).find((m)=>m.id===d||m.slug===d);if(r){N({id:r.id,name:r.name,slug:r.slug,role:r.role,timezone:r.timezone},e);return}}catch{}N({id:d,name:e.config.defaultWorkspaceName??null,slug:e.config.defaultWorkspaceSlug??null},e);return}default:throw new w(`Unknown workspace command "${n??""}". Try: list, use, current`,o.USAGE)}}async function Ae(e,n,u,t){R(e),k(e);let y=D(e);switch(n){case void 0:case"list":case"ls":{let d=await y.get("/api/social-accounts",{workspace:!0}),i=$(u,"platform","p")?.toLowerCase(),r=i?d.filter((m)=>{let O=(m.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"]}[i]??[i]).some((U)=>O.includes(U))}):d;N(r.map((m)=>({id:m.id,providerName:m.providerName,username:m.username??null,displayName:m.displayName??null,isConnected:m.isConnected,expiresAt:m.expiresAt??null})),e);return}default:throw new w(`Unknown accounts command "${n}". Try: list`,o.USAGE)}}var ve=/^(\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(e,n=new Date){let u=e.trim();if(!u)throw new w("Empty time value",o.VALIDATION);let t=u.match(ve);if(t){let i=Number(t[1]),r=t[2].toLowerCase(),m=Ce(r)*i;return new Date(n.getTime()+m)}let y=u.match(/^in\s+(.+)$/i);if(y)return C(y[1],n);let d=new Date(u);if(Number.isNaN(d.getTime()))throw new w(`Invalid time "${e}". Use ISO-8601 (2026-06-01T09:00:00Z) or relative (2h, 30m, 1d).`,o.VALIDATION);return d}function Ce(e){switch(e){case"s":case"sec":case"secs":case"second":case"seconds":return 1000;case"m":case"min":case"mins":case"minute":case"minutes":return 60000;case"h":case"hr":case"hrs":case"hour":case"hours":return 3600000;case"d":case"day":case"days":return 86400000;case"w":case"week":case"weeks":return 604800000;default:throw new w(`Unknown time unit: ${e}`,o.VALIDATION)}}function Z(e){if(e.at&&e.in)throw new w("Pass only one of --at or --in",o.VALIDATION);if(e.at)return C(e.at).toISOString();if(e.in)return C(e.in).toISOString();throw new w("Schedule time required. Pass --at <iso> or --in <duration> (e.g. 2h).",o.VALIDATION)}function Ue(e){if(!e)return[];return e.split(",").map((n)=>n.trim()).filter(Boolean)}function $e(e){let n=$(e,"text","content","c","t");if(!n)throw new w("Missing --text",o.VALIDATION);return n}function c(e){let n=p(e,"accounts","account","a")??Ue($(e,"accounts","account","a"));if(!n||n.length===0)throw new w("Missing --accounts <id,id>",o.VALIDATION);return n}function l(e){return p(e,"media","media-urls")??Ue($(e,"media","media-urls"))}function Y(e){let n=$(e,"meta","metadata","meta-json");if(!n)return;try{let u=JSON.parse(n);if(!u||typeof u!=="object"||Array.isArray(u))throw Error("metadata must be a JSON object");return u}catch(u){throw new w(`Invalid --meta JSON: ${u.message}`,o.VALIDATION)}}async function Te(e,n,u,t){R(e),k(e);let y=D(e);switch(n){case"list":case"ls":{let d=await y.get("/api/posts",{workspace:!0,query:{status:$(u,"status","s"),socialAccountId:$(u,"account","social-account"),groupId:$(u,"group"),limit:I(u,"limit")??50,offset:I(u,"offset")??0}});N(d,e);return}case"get":{let d=t[0];if(!d)throw new w("Usage: dun posts get <id>",o.USAGE);let i=await y.get(`/api/posts/${d}`,{workspace:!0});N(i,e);return}case"schedule":{let d=$e(u),i=c(u),r=Z({at:$(u,"at"),in:$(u,"in")}),m=await y.post("/api/posts/schedule",{content:d,socialAccountIds:i,scheduledAt:r,mediaUrls:l(u),metadata:Y(u)??{}},{workspace:!0});N(m,e);return}case"publish":case"publish-now":{let d=$e(u),i=c(u),r=await y.post("/api/posts/publish-now",{content:d,socialAccountIds:i,mediaUrls:l(u),metadata:Y(u)??{},naturalPosting:j(u,"natural","natural-posting")},{workspace:!0});N(r,e);return}case"reschedule":{let d=t[0];if(!d)throw new w("Usage: dun posts reschedule <id> --at|--in",o.USAGE);let r={scheduledAt:Z({at:$(u,"at"),in:$(u,"in")})},m=$(u,"text","content","c","t");if(m)r.content=m;let O=l(u);if(O.length)r.mediaUrls=O;let A=Y(u);if(A)r.metadata=A;if(!r.content){let U=await y.get(`/api/posts/${d}`,{workspace:!0});if(r.content=U.content,!r.metadata&&U.metadata)r.metadata=U.metadata;if(!r.mediaUrls&&U.mediaUrls)r.mediaUrls=U.mediaUrls}let T=await y.post(`/api/posts/${d}/reschedule`,r,{workspace:!0});N(T,e);return}case"cancel":{let d=t[0];if(!d)throw new w("Usage: dun posts cancel <id>",o.USAGE);let i=await y.post(`/api/posts/${d}/cancel`,void 0,{workspace:!0});N(i??{id:d,status:"cancelled"},e);return}case"delete":case"rm":{let d=t[0];if(!d)throw new w("Usage: dun posts delete <id> [--yes]",o.USAGE);if(!e.yes&&!j(u,"yes","y"))throw new w("Refusing to delete without --yes (or CI/yes mode)",o.VALIDATION);let i=await y.delete(`/api/posts/${d}`,void 0,{workspace:!0});N(i??{id:d,deleted:!0},e);return}case"x-cap":case"xcap":{let d=await y.get("/api/posts/x-cap-usage",{workspace:!0});N(d,e);return}case"schedule-thread":case"publish-thread":case"publish-thread-now":{let d=$(u,"file","f"),i=$(u,"account","accounts","a")??c(u)[0];if(!i)throw new w("Missing --account <xAccountId>",o.VALIDATION);let r;if(d){let O=await Bun.file(d).text(),A=JSON.parse(O);if(Array.isArray(A))r=A;else if(A&&typeof A==="object"&&Array.isArray(A.tweets))r=A.tweets;else throw new w("Thread file must be an array of tweets or { tweets: [] }",o.VALIDATION)}else{let O=$(u,"text","content");if(!O)throw new w("Provide --file thread.json (array of {content}) or informal --text with || separators is not enough alone for threads — use --file",o.VALIDATION);r=O.split("||").map((A)=>({content:A.trim()})).filter((A)=>A.content)}if(r.length<2)throw new w("Threads require at least 2 tweets",o.VALIDATION);if(n==="schedule-thread"){let O=Z({at:$(u,"at"),in:$(u,"in")}),A=await y.post("/api/posts/schedule-thread",{socialAccountId:i,tweets:r,scheduledAt:O,metadata:Y(u)??{}},{workspace:!0});N(A,e);return}let m=await y.post("/api/posts/publish-thread-now",{socialAccountId:i,tweets:r,metadata:Y(u)??{}},{workspace:!0});N(m,e);return}default:throw new w(`Unknown posts command "${n??""}". Try: list, get, schedule, publish, reschedule, cancel, delete, x-cap, schedule-thread, publish-thread`,o.USAGE)}}async function De(e,n,u,t){R(e),k(e);let y=D(e);switch(n){case"list":case"ls":{let d=await y.get("/api/drafts",{workspace:!0,query:{limit:I(u,"limit")??50,offset:I(u,"offset")??0}});N(d,e);return}case"get":{let d=t[0];if(!d)throw new w("Usage: dun drafts get <id>",o.USAGE);let i=await y.get(`/api/drafts/${d}`,{workspace:!0});N(i,e);return}case"create":{let d=$(u,"text","content","c","t");if(!d)throw new w("Missing --text",o.VALIDATION);let i=await y.post("/api/drafts",{content:d,name:$(u,"name","n"),socialAccountIds:p(u,"accounts","account","a")??[],selectedPlatforms:p(u,"platforms","platform","p")??[],mediaUrls:p(u,"media")??[]},{workspace:!0});N(i,e);return}case"update":{let d=t[0];if(!d)throw new w("Usage: dun drafts update <id> --text ...",o.USAGE);let i={id:d},r=$(u,"text","content","c","t");if(r)i.content=r;let m=$(u,"name","n");if(m!==void 0)i.name=m;let O=p(u,"accounts","account","a");if(O)i.socialAccountIds=O;let A=p(u,"platforms","platform","p");if(A)i.selectedPlatforms=A;let T=p(u,"media");if(T)i.mediaUrls=T;let U=await y.patch(`/api/drafts/${d}`,i,{workspace:!0});N(U,e);return}case"delete":case"rm":{let d=t[0];if(!d)throw new w("Usage: dun drafts delete <id> [--yes]",o.USAGE);if(!e.yes&&!j(u,"yes","y"))throw new w("Refusing to delete without --yes",o.VALIDATION);let i=await y.delete(`/api/drafts/${d}`,void 0,{workspace:!0});N(i??{id:d,deleted:!0},e);return}default:throw new w(`Unknown drafts command "${n??""}". Try: list, get, create, update, delete`,o.USAGE)}}import{basename as ce}from"node:path";function le(e){let n=e.split(".").pop()?.toLowerCase()??"";return{jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",mp4:"video/mp4",mov:"video/quicktime",webm:"video/webm"}[n]??"application/octet-stream"}async function Re(e,n,u,t){R(e),k(e);let y=D(e);switch(n){case"list":case"ls":{let d=await y.get("/api/media",{workspace:!0,query:{limit:I(u,"limit")??50,offset:I(u,"offset")??0}});N(d,e);return}case"get":{let d=t[0];if(!d)throw new w("Usage: dun media get <id>",o.USAGE);let i=await y.get(`/api/media/${d}`,{workspace:!0});N(i,e);return}case"upload":{let d=t[0]||$(u,"file","f");if(!d)throw new w('Usage: dun media upload <file> [--alt "..."]',o.USAGE);let i=Bun.file(d);if(!await i.exists())throw new w(`File not found: ${d}`,o.VALIDATION);let m=ce(d),O=$(u,"mime","type","content-type")||i.type||le(m),A=i.size,T=$(u,"alt","alt-text"),U=await y.post("/api/media/upload-url",{filename:m,mimeType:O,fileSize:A},{workspace:!0});H(e.debug,`PUT ${U.uploadUrl}`,{storageKey:U.storageKey,fileSize:A});let V=await i.arrayBuffer(),b;try{b=await fetch(U.uploadUrl,{method:"PUT",headers:{"Content-Type":O,"Content-Length":String(A)},body:V})}catch(z){throw new w(`Upload PUT failed: ${z.message}`,o.NETWORK)}if(!b.ok){let z=await b.text().catch(()=>"");throw new w(`Upload PUT failed with HTTP ${b.status}${z?`: ${z.slice(0,200)}`:""}`,o.NETWORK)}let _=await y.post("/api/media/complete",{storageKey:U.storageKey,originalFilename:m,mimeType:O,fileSize:A,altText:T},{workspace:!0});N(_,e);return}case"delete":case"rm":{let d=t[0];if(!d)throw new w("Usage: dun media delete <id> [--yes]",o.USAGE);if(!e.yes&&!j(u,"yes","y"))throw new w("Refusing to delete without --yes",o.VALIDATION);let i=await y.delete(`/api/media/${d}`,void 0,{workspace:!0});N(i??{id:d,deleted:!0},e);return}default:throw new w(`Unknown media command "${n??""}". Try: list, get, upload, delete`,o.USAGE)}}async function be(e,n,u,t){R(e),k(e);let y=D(e),d=n[0];if(d==="collections"||d==="collection"){let i=n[1]||"list";switch(i){case"list":case"ls":{let r=await y.get("/api/memory/collections",{workspace:!0});N(r,e);return}case"create":{let r=$(u,"name","n");if(!r)throw new w("Missing --name",o.VALIDATION);let m=await y.post("/api/memory/collections",{name:r,color:$(u,"color")??"blue",isPrivate:j(u,"private")},{workspace:!0});N(m,e);return}case"get":{let r=n[2]||t[0];if(!r)throw new w("Usage: dun memory collections get <id>",o.USAGE);let m=await y.get(`/api/memory/collections/${r}`,{workspace:!0});N(m,e);return}default:throw new w(`Unknown memory collections command "${i}". Try: list, create, get`,o.USAGE)}}switch(d){case"list":case"ls":{let i=$(u,"collection","collection-id","c");if(!i)throw new w("Missing --collection <id>",o.VALIDATION);let r=await y.get("/api/memory",{workspace:!0,query:{collectionId:i,limit:I(u,"limit")??100}});N(r,e);return}case"save":case"add":{let i=$(u,"collection","collection-id","c"),r=$(u,"text","t");if(!i)throw new w("Missing --collection <id>",o.VALIDATION);if(!r)throw new w("Missing --text",o.VALIDATION);let m=await y.post("/api/memory",{collectionId:i,text:r},{workspace:!0});N(m,e);return}case"search":{let i=$(u,"prompt","q","query","text"),r=p(u,"collections","collection","collection-ids","c")??[];if(!i)throw new w("Missing --prompt",o.VALIDATION);if(r.length===0)throw new w("Missing --collections <id,id>",o.VALIDATION);let m=await y.post("/api/memory/search",{prompt:i,collectionIds:r,topK:I(u,"top-k","topk","k")??5},{workspace:!0});N(m,e);return}case"delete":case"rm":{let i=n[1]||t[0],r=$(u,"collection","collection-id","c");if(!i)throw new w("Usage: dun memory delete <id> --collection <id>",o.USAGE);if(!r)throw new w("Missing --collection <id>",o.VALIDATION);if(!e.yes&&!j(u,"yes","y"))throw new w("Refusing to delete without --yes",o.VALIDATION);let m=await y.delete(`/api/memory/${i}`,{collectionId:r,memoryId:i},{workspace:!0});N(m??{id:i,deleted:!0},e);return}default:throw new w(`Unknown memory command "${d??""}". Try: collections, list, save, search, delete`,o.USAGE)}}import{mkdir as g,writeFile as x,readFile as ke}from"node:fs/promises";import{dirname as B,join as h}from"node:path";import{homedir as a}from"node:os";import{fileURLToPath as ge}from"node:url";function ee(){let e=B(ge(import.meta.url));return h(e,"..","..","skills","dunsocial","SKILL.md")}async function xe(){try{return await ke(ee(),"utf8")}catch{throw new w(`Bundled skill not found at ${ee()}. Reinstall @dunsocial/cli.`,o.USAGE)}}async function je(e,n,u,t){switch(n){case"path":{N({path:ee()},e);return}case"install":{let y=(t[0]||"").toLowerCase();if(!y||y!=="claude"&&y!=="codex")throw new w("Usage: dun skill install claude|codex",o.USAGE);let d=await xe();if(y==="claude"){let _=h(a(),".claude","skills","dunsocial","SKILL.md");await g(B(_),{recursive:!0}),await x(_,d,"utf8"),N({target:"claude",path:_,message:"Installed DunSocial skill for Claude Code. Restart or open a new session if needed."},e);return}let i=h(a(),".codex","skills","dunsocial","SKILL.md");await g(B(i),{recursive:!0}),await x(i,d,"utf8");let r=h(a(),".codex","AGENTS.md");await g(B(r),{recursive:!0});let m="";try{m=await ke(r,"utf8")}catch{m=""}let O=["<!-- BEGIN DUNSOCIAL CLI SKILL -->","## DunSocial CLI","","Use the `dun` CLI for DunSocial scheduling from the shell. Prefer `--json` output.",`Skill details: ${i}`,"","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(`
|
|
136
|
+
`);if(y&&process.stdout.isTTY)try{await fe(t.verificationUrlComplete)}catch{}let d=Math.max(3,t.interval||5)*1000,i=Date.now()+(t.expiresIn||900)*1000;while(Date.now()<i){await Ee(d);let r=await u.post("/api/cli/auth/poll",{deviceCode:t.deviceCode},{auth:!1});if(r.status==="pending")continue;if(r.status==="expired")throw new w("Login expired. Run dun auth login again.",o.AUTH);if(r.status==="denied")throw new w("Login was denied in the browser.",o.AUTH);if(r.status==="approved"){let m=r.token,O={...e,token:m},A=D(O),T=await A.get("/api/user/profile"),U={...e.config,apiUrl:e.apiUrl,token:m,defaultWorkspaceId:r.workspaceId||e.config.defaultWorkspaceId,defaultWorkspaceSlug:e.config.defaultWorkspaceSlug,defaultWorkspaceName:e.config.defaultWorkspaceName};if(r.workspaceId)try{let b=(await A.get("/api/workspaces")).find((_)=>_.id===r.workspaceId);if(b)U.defaultWorkspaceId=b.id,U.defaultWorkspaceSlug=b.slug,U.defaultWorkspaceName=b.name}catch{}await H(U),N({user:{id:T.id,name:T.name,email:T.email},workspaceId:U.defaultWorkspaceId??null,configPath:J(),method:"device",message:U.defaultWorkspaceId?"Logged in.":"Logged in. Next: dun workspace list && dun workspace use <id|slug>"},e);return}}throw new w("Login timed out. Run dun auth login again.",o.AUTH)}async function Pe(e){await oe(),N({message:"Logged out. Config cleared.",configPath:J()},e)}async function se(e){R(e);let u=await D(e).get("/api/user/profile");N({id:u.id,name:u.name,email:u.email,image:u.image??null,apiUrl:e.apiUrl,workspaceId:e.workspaceId??null},e)}async function Se(e){let n=Boolean(e.token),u=null,t=null;if(n)try{u=await D(e).get("/api/user/profile")}catch(y){t=y instanceof Error?y.message:String(y)}N({authenticated:Boolean(u),tokenPresent:n,apiUrl:e.apiUrl,workspaceId:e.workspaceId??null,workspaceSlug:e.config.defaultWorkspaceSlug??null,workspaceName:e.config.defaultWorkspaceName??null,configPath:J(),user:u?{id:u.id,name:u.name,email:u.email}:null,error:t},e)}function Ee(e){return new Promise((n)=>setTimeout(n,e))}async function fe(e){let n=process.platform,u=n==="darwin"?["open",e]:n==="win32"?["cmd","/c","start","",e]:["xdg-open",e];await Bun.spawn(u,{stdout:"ignore",stderr:"ignore"}).exited}async function Ne(e,n,u,t){R(e);let y=D(e);switch(n){case"list":case"ls":{let d=await y.get("/api/workspaces");N(d.map((i)=>({id:i.id,name:i.name,slug:i.slug,role:i.role,timezone:i.timezone,plan:i.subscription?.planName??null,status:i.subscription?.status??null})),e);return}case"use":{let d=t[0];if(!d)throw new w("Usage: dun workspace use <id|slug>",o.USAGE);let r=(await y.get("/api/workspaces")).find((m)=>m.id===d||m.slug===d);if(!r)throw new w(`Workspace not found: ${d}. Run dun workspace list`,o.NOT_FOUND);await H({...e.config,apiUrl:e.apiUrl,token:e.token,defaultWorkspaceId:r.id,defaultWorkspaceSlug:r.slug,defaultWorkspaceName:r.name}),N({id:r.id,name:r.name,slug:r.slug,role:r.role,message:`Default workspace set to ${r.name}`},e);return}case"current":{if(!e.workspaceId&&!e.config.defaultWorkspaceId)throw new w("No workspace selected. Run dun workspace use <id|slug>",o.VALIDATION);let d=e.workspaceId||e.config.defaultWorkspaceId;try{let r=(await y.get("/api/workspaces")).find((m)=>m.id===d||m.slug===d);if(r){N({id:r.id,name:r.name,slug:r.slug,role:r.role,timezone:r.timezone},e);return}}catch{}N({id:d,name:e.config.defaultWorkspaceName??null,slug:e.config.defaultWorkspaceSlug??null},e);return}default:throw new w(`Unknown workspace command "${n??""}". Try: list, use, current`,o.USAGE)}}async function Ae(e,n,u,t){R(e),k(e);let y=D(e);switch(n){case void 0:case"list":case"ls":{let d=await y.get("/api/social-accounts",{workspace:!0}),i=$(u,"platform","p")?.toLowerCase(),r=i?d.filter((m)=>{let O=(m.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"]}[i]??[i]).some((U)=>O.includes(U))}):d;N(r.map((m)=>({id:m.id,providerName:m.providerName,username:m.username??null,displayName:m.displayName??null,isConnected:m.isConnected,expiresAt:m.expiresAt??null})),e);return}default:throw new w(`Unknown accounts command "${n}". Try: list`,o.USAGE)}}var ve=/^(\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(e,n=new Date){let u=e.trim();if(!u)throw new w("Empty time value",o.VALIDATION);let t=u.match(ve);if(t){let i=Number(t[1]),r=t[2].toLowerCase(),m=Ce(r)*i;return new Date(n.getTime()+m)}let y=u.match(/^in\s+(.+)$/i);if(y)return C(y[1],n);let d=new Date(u);if(Number.isNaN(d.getTime()))throw new w(`Invalid time "${e}". Use ISO-8601 (2026-06-01T09:00:00Z) or relative (2h, 30m, 1d).`,o.VALIDATION);return d}function Ce(e){switch(e){case"s":case"sec":case"secs":case"second":case"seconds":return 1000;case"m":case"min":case"mins":case"minute":case"minutes":return 60000;case"h":case"hr":case"hrs":case"hour":case"hours":return 3600000;case"d":case"day":case"days":return 86400000;case"w":case"week":case"weeks":return 604800000;default:throw new w(`Unknown time unit: ${e}`,o.VALIDATION)}}function B(e){if(e.at&&e.in)throw new w("Pass only one of --at or --in",o.VALIDATION);if(e.at)return C(e.at).toISOString();if(e.in)return C(e.in).toISOString();throw new w("Schedule time required. Pass --at <iso> or --in <duration> (e.g. 2h).",o.VALIDATION)}function Ue(e){if(!e)return[];return e.split(",").map((n)=>n.trim()).filter(Boolean)}function $e(e){let n=$(e,"text","content","c","t");if(!n)throw new w("Missing --text",o.VALIDATION);return n}function c(e){let n=p(e,"accounts","account","a")??Ue($(e,"accounts","account","a"));if(!n||n.length===0)throw new w("Missing --accounts <id,id>",o.VALIDATION);return n}function l(e){return p(e,"media","media-urls")??Ue($(e,"media","media-urls"))}function z(e){let n=$(e,"meta","metadata","meta-json");if(!n)return;try{let u=JSON.parse(n);if(!u||typeof u!=="object"||Array.isArray(u))throw Error("metadata must be a JSON object");return u}catch(u){throw new w(`Invalid --meta JSON: ${u.message}`,o.VALIDATION)}}async function Te(e,n,u,t){R(e),k(e);let y=D(e);switch(n){case"list":case"ls":{let d=await y.get("/api/posts",{workspace:!0,query:{status:$(u,"status","s"),socialAccountId:$(u,"account","social-account"),groupId:$(u,"group"),limit:I(u,"limit")??50,offset:I(u,"offset")??0}});N(d,e);return}case"get":{let d=t[0];if(!d)throw new w("Usage: dun posts get <id>",o.USAGE);let i=await y.get(`/api/posts/${d}`,{workspace:!0});N(i,e);return}case"schedule":{let d=$e(u),i=c(u),r=B({at:$(u,"at"),in:$(u,"in")}),m=await y.post("/api/posts/schedule",{content:d,socialAccountIds:i,scheduledAt:r,mediaUrls:l(u),metadata:z(u)??{}},{workspace:!0});N(m,e);return}case"publish":case"publish-now":{let d=$e(u),i=c(u),r=await y.post("/api/posts/publish-now",{content:d,socialAccountIds:i,mediaUrls:l(u),metadata:z(u)??{},naturalPosting:j(u,"natural","natural-posting")},{workspace:!0});N(r,e);return}case"reschedule":{let d=t[0];if(!d)throw new w("Usage: dun posts reschedule <id> --at|--in",o.USAGE);let r={scheduledAt:B({at:$(u,"at"),in:$(u,"in")})},m=$(u,"text","content","c","t");if(m)r.content=m;let O=l(u);if(O.length)r.mediaUrls=O;let A=z(u);if(A)r.metadata=A;if(!r.content){let U=await y.get(`/api/posts/${d}`,{workspace:!0});if(r.content=U.content,!r.metadata&&U.metadata)r.metadata=U.metadata;if(!r.mediaUrls&&U.mediaUrls)r.mediaUrls=U.mediaUrls}let T=await y.post(`/api/posts/${d}/reschedule`,r,{workspace:!0});N(T,e);return}case"cancel":{let d=t[0];if(!d)throw new w("Usage: dun posts cancel <id>",o.USAGE);let i=await y.post(`/api/posts/${d}/cancel`,void 0,{workspace:!0});N(i??{id:d,status:"cancelled"},e);return}case"delete":case"rm":{let d=t[0];if(!d)throw new w("Usage: dun posts delete <id> [--yes]",o.USAGE);if(!e.yes&&!j(u,"yes","y"))throw new w("Refusing to delete without --yes (or CI/yes mode)",o.VALIDATION);let i=await y.delete(`/api/posts/${d}`,void 0,{workspace:!0});N(i??{id:d,deleted:!0},e);return}case"x-cap":case"xcap":{let d=await y.get("/api/posts/x-cap-usage",{workspace:!0});N(d,e);return}case"schedule-thread":case"publish-thread":case"publish-thread-now":{let d=$(u,"file","f"),i=$(u,"account","accounts","a")??c(u)[0];if(!i)throw new w("Missing --account <xAccountId>",o.VALIDATION);let r;if(d){let O=await Bun.file(d).text(),A=JSON.parse(O);if(Array.isArray(A))r=A;else if(A&&typeof A==="object"&&Array.isArray(A.tweets))r=A.tweets;else throw new w("Thread file must be an array of tweets or { tweets: [] }",o.VALIDATION)}else{let O=$(u,"text","content");if(!O)throw new w("Provide --file thread.json (array of {content}) or informal --text with || separators is not enough alone for threads — use --file",o.VALIDATION);r=O.split("||").map((A)=>({content:A.trim()})).filter((A)=>A.content)}if(r.length<2)throw new w("Threads require at least 2 tweets",o.VALIDATION);if(n==="schedule-thread"){let O=B({at:$(u,"at"),in:$(u,"in")}),A=await y.post("/api/posts/schedule-thread",{socialAccountId:i,tweets:r,scheduledAt:O,metadata:z(u)??{}},{workspace:!0});N(A,e);return}let m=await y.post("/api/posts/publish-thread-now",{socialAccountId:i,tweets:r,metadata:z(u)??{}},{workspace:!0});N(m,e);return}default:throw new w(`Unknown posts command "${n??""}". Try: list, get, schedule, publish, reschedule, cancel, delete, x-cap, schedule-thread, publish-thread`,o.USAGE)}}async function De(e,n,u,t){R(e),k(e);let y=D(e);switch(n){case"list":case"ls":{let d=await y.get("/api/drafts",{workspace:!0,query:{limit:I(u,"limit")??50,offset:I(u,"offset")??0}});N(d,e);return}case"get":{let d=t[0];if(!d)throw new w("Usage: dun drafts get <id>",o.USAGE);let i=await y.get(`/api/drafts/${d}`,{workspace:!0});N(i,e);return}case"create":{let d=$(u,"text","content","c","t");if(!d)throw new w("Missing --text",o.VALIDATION);let i=await y.post("/api/drafts",{content:d,name:$(u,"name","n"),socialAccountIds:p(u,"accounts","account","a")??[],selectedPlatforms:p(u,"platforms","platform","p")??[],mediaUrls:p(u,"media")??[]},{workspace:!0});N(i,e);return}case"update":{let d=t[0];if(!d)throw new w("Usage: dun drafts update <id> --text ...",o.USAGE);let i={id:d},r=$(u,"text","content","c","t");if(r)i.content=r;let m=$(u,"name","n");if(m!==void 0)i.name=m;let O=p(u,"accounts","account","a");if(O)i.socialAccountIds=O;let A=p(u,"platforms","platform","p");if(A)i.selectedPlatforms=A;let T=p(u,"media");if(T)i.mediaUrls=T;let U=await y.patch(`/api/drafts/${d}`,i,{workspace:!0});N(U,e);return}case"delete":case"rm":{let d=t[0];if(!d)throw new w("Usage: dun drafts delete <id> [--yes]",o.USAGE);if(!e.yes&&!j(u,"yes","y"))throw new w("Refusing to delete without --yes",o.VALIDATION);let i=await y.delete(`/api/drafts/${d}`,void 0,{workspace:!0});N(i??{id:d,deleted:!0},e);return}default:throw new w(`Unknown drafts command "${n??""}". Try: list, get, create, update, delete`,o.USAGE)}}import{basename as ce}from"node:path";function le(e){let n=e.split(".").pop()?.toLowerCase()??"";return{jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",mp4:"video/mp4",mov:"video/quicktime",webm:"video/webm"}[n]??"application/octet-stream"}async function Re(e,n,u,t){R(e),k(e);let y=D(e);switch(n){case"list":case"ls":{let d=await y.get("/api/media",{workspace:!0,query:{limit:I(u,"limit")??50,offset:I(u,"offset")??0}});N(d,e);return}case"get":{let d=t[0];if(!d)throw new w("Usage: dun media get <id>",o.USAGE);let i=await y.get(`/api/media/${d}`,{workspace:!0});N(i,e);return}case"upload":{let d=t[0]||$(u,"file","f");if(!d)throw new w('Usage: dun media upload <file> [--alt "..."]',o.USAGE);let i=Bun.file(d);if(!await i.exists())throw new w(`File not found: ${d}`,o.VALIDATION);let m=ce(d),O=$(u,"mime","type","content-type")||i.type||le(m),A=i.size,T=$(u,"alt","alt-text"),U=await y.post("/api/media/upload-url",{filename:m,mimeType:O,fileSize:A},{workspace:!0});Y(e.debug,`PUT ${U.uploadUrl}`,{storageKey:U.storageKey,fileSize:A});let V=await i.arrayBuffer(),b;try{b=await fetch(U.uploadUrl,{method:"PUT",headers:{"Content-Type":O,"Content-Length":String(A)},body:V})}catch(W){throw new w(`Upload PUT failed: ${W.message}`,o.NETWORK)}if(!b.ok){let W=await b.text().catch(()=>"");throw new w(`Upload PUT failed with HTTP ${b.status}${W?`: ${W.slice(0,200)}`:""}`,o.NETWORK)}let _=await y.post("/api/media/complete",{storageKey:U.storageKey,originalFilename:m,mimeType:O,fileSize:A,altText:T},{workspace:!0});N(_,e);return}case"delete":case"rm":{let d=t[0];if(!d)throw new w("Usage: dun media delete <id> [--yes]",o.USAGE);if(!e.yes&&!j(u,"yes","y"))throw new w("Refusing to delete without --yes",o.VALIDATION);let i=await y.delete(`/api/media/${d}`,void 0,{workspace:!0});N(i??{id:d,deleted:!0},e);return}default:throw new w(`Unknown media command "${n??""}". Try: list, get, upload, delete`,o.USAGE)}}async function be(e,n,u,t){R(e),k(e);let y=D(e),d=n[0];if(d==="collections"||d==="collection"){let i=n[1]||"list";switch(i){case"list":case"ls":{let r=await y.get("/api/memory/collections",{workspace:!0});N(r,e);return}case"create":{let r=$(u,"name","n");if(!r)throw new w("Missing --name",o.VALIDATION);let m=await y.post("/api/memory/collections",{name:r,color:$(u,"color")??"blue",isPrivate:j(u,"private")},{workspace:!0});N(m,e);return}case"get":{let r=n[2]||t[0];if(!r)throw new w("Usage: dun memory collections get <id>",o.USAGE);let m=await y.get(`/api/memory/collections/${r}`,{workspace:!0});N(m,e);return}default:throw new w(`Unknown memory collections command "${i}". Try: list, create, get`,o.USAGE)}}switch(d){case"list":case"ls":{let i=$(u,"collection","collection-id","c");if(!i)throw new w("Missing --collection <id>",o.VALIDATION);let r=await y.get("/api/memory",{workspace:!0,query:{collectionId:i,limit:I(u,"limit")??100}});N(r,e);return}case"save":case"add":{let i=$(u,"collection","collection-id","c"),r=$(u,"text","t");if(!i)throw new w("Missing --collection <id>",o.VALIDATION);if(!r)throw new w("Missing --text",o.VALIDATION);let m=await y.post("/api/memory",{collectionId:i,text:r},{workspace:!0});N(m,e);return}case"search":{let i=$(u,"prompt","q","query","text"),r=p(u,"collections","collection","collection-ids","c")??[];if(!i)throw new w("Missing --prompt",o.VALIDATION);if(r.length===0)throw new w("Missing --collections <id,id>",o.VALIDATION);let m=await y.post("/api/memory/search",{prompt:i,collectionIds:r,topK:I(u,"top-k","topk","k")??5},{workspace:!0});N(m,e);return}case"delete":case"rm":{let i=n[1]||t[0],r=$(u,"collection","collection-id","c");if(!i)throw new w("Usage: dun memory delete <id> --collection <id>",o.USAGE);if(!r)throw new w("Missing --collection <id>",o.VALIDATION);if(!e.yes&&!j(u,"yes","y"))throw new w("Refusing to delete without --yes",o.VALIDATION);let m=await y.delete(`/api/memory/${i}`,{collectionId:r,memoryId:i},{workspace:!0});N(m??{id:i,deleted:!0},e);return}default:throw new w(`Unknown memory command "${d??""}". Try: collections, list, save, search, delete`,o.USAGE)}}import{existsSync as ge}from"node:fs";import{mkdir as g,writeFile as x,readFile as ke}from"node:fs/promises";import{dirname as F,join as L}from"node:path";import{homedir as a}from"node:os";import{fileURLToPath as xe}from"node:url";function ee(){let e=F(xe(import.meta.url)),n=L("skills","dunsocial","SKILL.md"),u=[L(e,"..",n),L(e,"..","..",n)];return u.find(ge)??u[0]}async function ae(){try{return await ke(ee(),"utf8")}catch{throw new w(`Bundled skill not found at ${ee()}. Reinstall dunsocial.`,o.USAGE)}}async function je(e,n,u,t){switch(n){case"path":{N({path:ee()},e);return}case"install":{let y=(t[0]||"").toLowerCase();if(!y||y!=="claude"&&y!=="codex")throw new w("Usage: dun skill install claude|codex",o.USAGE);let d=await ae();if(y==="claude"){let _=L(a(),".claude","skills","dunsocial","SKILL.md");await g(F(_),{recursive:!0}),await x(_,d,"utf8"),N({target:"claude",path:_,message:"Installed DunSocial skill for Claude Code. Restart or open a new session if needed."},e);return}let i=L(a(),".codex","skills","dunsocial","SKILL.md");await g(F(i),{recursive:!0}),await x(i,d,"utf8");let r=L(a(),".codex","AGENTS.md");await g(F(r),{recursive:!0});let m="";try{m=await ke(r,"utf8")}catch{m=""}let O=["<!-- BEGIN DUNSOCIAL CLI SKILL -->","## DunSocial CLI","","Use the `dun` CLI for DunSocial scheduling from the shell. Prefer `--json` output.",`Skill details: ${i}`,"","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(`
|
|
137
137
|
`),A="<!-- BEGIN DUNSOCIAL CLI SKILL -->",T="<!-- END DUNSOCIAL CLI SKILL -->",U,V=m.indexOf(A),b=m.indexOf(T);if(V!==-1&&b!==-1&&b>V)U=m.slice(0,V)+O+m.slice(b+T.length);else if(m.trim())U=`${m.trimEnd()}
|
|
138
138
|
|
|
139
139
|
${O}
|
|
140
140
|
`;else U=`${O}
|
|
141
|
-
`;await x(r,U,"utf8"),N({target:"codex",skillPath:i,agentsPath:r,message:"Installed DunSocial skill for Codex."},e);return}default:throw new w(`Unknown skill command "${n??""}". Try: install, path`,o.USAGE)}}async function
|
|
142
|
-
`),G.OK;if(u==="help"||!u&&
|
|
143
|
-
`:`${
|
|
144
|
-
`),G.OK}if(!u)return process.stdout.write(`${
|
|
145
|
-
`),G.OK;if(
|
|
146
|
-
`),G.OK;let r=await de({apiUrl:
|
|
141
|
+
`;await x(r,U,"utf8"),N({target:"codex",skillPath:i,agentsPath:r,message:"Installed DunSocial skill for Codex."},e);return}default:throw new w(`Unknown skill command "${n??""}". Try: install, path`,o.USAGE)}}async function en(e){let n=re(e),{group:u,action:t,nest:y,flags:d,positionals:i}=n;if(h(d)&&!u)return process.stdout.write(`${M}
|
|
142
|
+
`),G.OK;if(u==="help"||!u&&q(d)){let m=u==="help"?t||i[0]:void 0;return process.stdout.write(m?`${f(m)}
|
|
143
|
+
`:`${Z}
|
|
144
|
+
`),G.OK}if(!u)return process.stdout.write(`${Z}
|
|
145
|
+
`),G.OK;if(q(d))return process.stdout.write(`${f(u)}
|
|
146
|
+
`),G.OK;let r=await de({apiUrl:Q(d,"api-url","apiUrl"),token:Q(d,"token"),workspace:Q(d,"workspace","w"),json:K(d,"json","j")?!0:void 0,debug:K(d,"debug"),quiet:K(d,"quiet","q"),yes:K(d,"yes","y")});if(h(d))return s({version:M},r),G.OK;try{switch(u){case"auth":await Oe(r,t,d,i);break;case"workspace":case"workspaces":await Ne(r,t,d,i);break;case"account":case"accounts":await Ae(r,t??"list",d,i);break;case"post":case"posts":await Te(r,t,d,i);break;case"draft":case"drafts":await De(r,t,d,i);break;case"media":await Re(r,t,d,i);break;case"memory":{let m=y?[y,t].filter(Boolean):t?[t]:[];await be(r,m,d,i);break}case"skill":case"skills":await je(r,t,d,i);break;case"version":s({version:M},r);break;default:throw new S(`Unknown command "${u}". Run dun --help`,G.USAGE)}return G.OK}catch(m){return ie(m,r)}}var nn=await en(process.argv.slice(2));process.exit(nn);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dunsocial",
|
|
3
|
-
"version": "0.2
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "DunSocial agent-first CLI — schedule and publish social posts from the terminal, CI, and coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"mcp"
|
|
23
23
|
],
|
|
24
24
|
"bin": {
|
|
25
|
-
"dun": "
|
|
26
|
-
"dunsocial": "
|
|
25
|
+
"dun": "dist/index.js",
|
|
26
|
+
"dunsocial": "dist/index.js"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"dist",
|
|
@@ -1,44 +1,53 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dunsocial
|
|
3
|
-
description: Schedule, publish, and manage DunSocial posts, drafts, media, and memory from the shell via the dun CLI. Use when the user wants to post or schedule to X/LinkedIn/Bluesky/Threads/Reddit/Pinterest/Instagram/YouTube through DunSocial, manage drafts or media uploads, search workspace memory, or wire social publishing into CI/agent workflows.
|
|
3
|
+
description: Schedule, publish, and manage DunSocial posts, drafts, media, and memory from the shell via the dun CLI (npm package dunsocial). Use when the user wants to post or schedule to X/LinkedIn/Bluesky/Threads/Reddit/Pinterest/Instagram/YouTube through DunSocial, manage drafts or media uploads, search workspace memory, or wire social publishing into CI/agent workflows.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# DunSocial CLI skill
|
|
7
7
|
|
|
8
|
-
Use the
|
|
8
|
+
Use the **`dun`** binary from the **`dunsocial`** npm package.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g dunsocial
|
|
12
|
+
# or
|
|
13
|
+
npx dunsocial …
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Prefer **`--json`** on every command so output is machine-parseable.
|
|
17
|
+
|
|
18
|
+
Remote hosts (ChatGPT web, Claude web) should keep using DunSocial **MCP**. This CLI is for **local agents, terminals, and CI**.
|
|
11
19
|
|
|
12
20
|
## Auth + workspace
|
|
13
21
|
|
|
14
22
|
```bash
|
|
15
|
-
# Interactive (
|
|
23
|
+
# Interactive (browser approve at app.dunsocial.com/cli/authorize)
|
|
16
24
|
dun auth login
|
|
17
25
|
|
|
18
|
-
# CI / headless —
|
|
19
|
-
export DUN_TOKEN="
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
# CI / headless — PAT from app Settings → CLI (dun_pat_…)
|
|
27
|
+
export DUN_TOKEN="dun_pat_…"
|
|
28
|
+
export DUN_WORKSPACE_ID="…"
|
|
29
|
+
# optional:
|
|
30
|
+
export DUN_API_URL=https://api.dunsocial.com
|
|
22
31
|
|
|
23
32
|
dun auth status --json
|
|
24
33
|
dun workspace list --json
|
|
25
34
|
dun workspace use <id-or-slug> --json
|
|
26
35
|
```
|
|
27
36
|
|
|
28
|
-
Env overrides
|
|
37
|
+
Env overrides config file `~/.config/dunsocial/config.json`.
|
|
29
38
|
|
|
30
|
-
|
|
31
|
-
- `DUN_WORKSPACE_ID`
|
|
32
|
-
- `DUN_API_URL` (default `https://api.dunsocial.com`)
|
|
39
|
+
PAT notes:
|
|
33
40
|
|
|
34
|
-
|
|
41
|
+
- Workspace-bound; missing scope → exit `3` / forbidden
|
|
42
|
+
- Cannot manage other tokens, billing, or team
|
|
43
|
+
- Prefer PAT over full browser session tokens in CI
|
|
35
44
|
|
|
36
45
|
## Discovery order (always)
|
|
37
46
|
|
|
38
|
-
1. `dun auth status --json`
|
|
39
|
-
2. `dun workspace current --json` or `list` + `use`
|
|
47
|
+
1. `dun auth status --json`
|
|
48
|
+
2. `dun workspace current --json` (or `list` + `use`)
|
|
40
49
|
3. `dun accounts list --json` — capture social account **ids**
|
|
41
|
-
4. Act (schedule/publish/draft/media/memory)
|
|
50
|
+
4. Act (schedule / publish / draft / media / memory)
|
|
42
51
|
|
|
43
52
|
Do **not** invent account IDs. Do **not** treat `drafts create` as a successful publish.
|
|
44
53
|
|
|
@@ -46,17 +55,13 @@ Do **not** invent account IDs. Do **not** treat `drafts create` as a successful
|
|
|
46
55
|
|
|
47
56
|
```bash
|
|
48
57
|
dun posts schedule \
|
|
49
|
-
--text "Shipped the DunSocial CLI
|
|
58
|
+
--text "Shipped from the DunSocial CLI." \
|
|
50
59
|
--accounts acc_x,acc_li \
|
|
51
60
|
--in 1h \
|
|
52
61
|
--json
|
|
53
62
|
```
|
|
54
63
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
dun posts schedule --text "..." --accounts acc_x --at 2026-06-01T09:00:00Z --json
|
|
59
|
-
```
|
|
64
|
+
Absolute time: `--at 2026-06-01T09:00:00Z`.
|
|
60
65
|
|
|
61
66
|
## Publish now
|
|
62
67
|
|
|
@@ -68,36 +73,24 @@ dun posts publish --text "Hello from dun" --accounts acc_x --json
|
|
|
68
73
|
|
|
69
74
|
```bash
|
|
70
75
|
dun media upload ./shot.png --alt "Product screenshot" --json
|
|
71
|
-
|
|
72
|
-
dun posts schedule --text "..." --accounts acc_x --media <assetId> --in 30m --json
|
|
76
|
+
dun posts schedule --text "…" --accounts acc_x --media <assetId> --in 30m --json
|
|
73
77
|
```
|
|
74
78
|
|
|
75
|
-
## Drafts
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
dun drafts create --text "WIP idea" --platforms x,linkedin --json
|
|
79
|
-
dun drafts list --json
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
## Memory
|
|
79
|
+
## Drafts / memory / threads
|
|
83
80
|
|
|
84
81
|
```bash
|
|
82
|
+
dun drafts create --text "WIP" --platforms x,linkedin --json
|
|
85
83
|
dun memory collections list --json
|
|
86
|
-
dun memory save --collection <id> --text "
|
|
87
|
-
dun memory search --prompt "
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## Threads (X)
|
|
84
|
+
dun memory save --collection <id> --text "…" --json
|
|
85
|
+
dun memory search --prompt "…" --collections <id> --json
|
|
91
86
|
|
|
92
|
-
|
|
93
|
-
# thread.json => [ {"content":"1"}, {"content":"2"} ] or { "tweets": [...] }
|
|
87
|
+
# thread.json: [ {"content":"1"}, {"content":"2"} ]
|
|
94
88
|
dun posts schedule-thread --account acc_x --file ./thread.json --in 2h --json
|
|
95
|
-
dun posts publish-thread --account acc_x --file ./thread.json --json
|
|
96
89
|
```
|
|
97
90
|
|
|
98
91
|
## Destructive ops
|
|
99
92
|
|
|
100
|
-
Require `--yes` (or `CI=1`) for `delete
|
|
93
|
+
Require `--yes` (or `CI=1`) for `delete`.
|
|
101
94
|
|
|
102
95
|
## Exit codes
|
|
103
96
|
|
|
@@ -106,25 +99,25 @@ Require `--yes` (or `CI=1`) for `delete` commands.
|
|
|
106
99
|
| 0 | ok |
|
|
107
100
|
| 1 | usage |
|
|
108
101
|
| 2 | auth |
|
|
109
|
-
| 3 | forbidden |
|
|
102
|
+
| 3 | forbidden / missing scope |
|
|
110
103
|
| 4 | validation |
|
|
111
104
|
| 5 | not found |
|
|
112
105
|
| 6 | rate limited |
|
|
113
106
|
| 7 | network |
|
|
114
107
|
|
|
115
|
-
|
|
108
|
+
Failure JSON:
|
|
116
109
|
|
|
117
110
|
```json
|
|
118
|
-
{ "ok": false, "error": { "code": "validation", "message": "
|
|
111
|
+
{ "ok": false, "error": { "code": "validation", "message": "…", "details": null } }
|
|
119
112
|
```
|
|
120
113
|
|
|
121
114
|
## Pitfalls
|
|
122
115
|
|
|
123
|
-
-
|
|
124
|
-
- `--accounts`
|
|
125
|
-
-
|
|
126
|
-
- Platform-specific
|
|
127
|
-
- MCP OAuth
|
|
116
|
+
- Need workspace: `workspace use` or `--workspace` / `DUN_WORKSPACE_ID`
|
|
117
|
+
- `--accounts` = DunSocial social account **ids**, not handles
|
|
118
|
+
- Upload local files with `media upload` before attaching
|
|
119
|
+
- Platform-specific options via `--meta '{"reddit":{…}}'` when needed
|
|
120
|
+
- MCP OAuth and CLI tokens are different systems
|
|
128
121
|
|
|
129
122
|
## Help
|
|
130
123
|
|