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 CHANGED
@@ -1,157 +1,102 @@
1
- # DunSocial CLI (`dun`)
1
+ # DunSocial CLI
2
2
 
3
- Agent-first command line client for [DunSocial](https://dunsocial.com).
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
- bun install
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
- ### Built package (Node 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
- # npm (when published)
27
- npm i -g dunsocial
28
- dun --help
29
- ```
13
+ ---
30
14
 
31
- Bins: `dun` · `dunsocial`. Package name: `dunsocial`.
15
+ ## Get started
32
16
 
33
- ## Quick start
17
+ ### 1. Sign in
34
18
 
35
19
  ```bash
36
- # 1. Authenticate with a Better Auth bearer session token
37
- export DUN_TOKEN=... # or: dun auth login --token ...
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
- # 3. Discover connected accounts
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
- # 4. Schedule
48
- dun posts schedule \
49
- --text "Hello from the DunSocial CLI" \
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
- ## Auth
56
-
57
- ### Device login (default)
30
+ ### 3. See connected accounts
58
31
 
59
32
  ```bash
60
- dun auth login
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
- ### PAT / CI (recommended for automation)
36
+ Copy the **account id** you want to post with (not the @username).
66
37
 
67
- Create a token in **Settings → CLI** (hashed at rest, scoped, revocable):
38
+ ### 4. Schedule a post
68
39
 
69
40
  ```bash
70
- export DUN_TOKEN='dun_pat_…'
71
- export DUN_WORKSPACE_ID='…'
72
- dun auth status --json
41
+ dun posts schedule \
42
+ --text "Hello from DunSocial" \
43
+ --accounts ACCOUNT_ID \
44
+ --in 1h
73
45
  ```
74
46
 
75
- ### Session token (humans / legacy)
47
+ Post right away:
76
48
 
77
49
  ```bash
78
- dun auth login --token <bearer-session>
79
- export DUN_TOKEN=...
80
- dun auth whoami --json
81
- dun auth logout
50
+ dun posts publish \
51
+ --text "Hello from DunSocial" \
52
+ --accounts ACCOUNT_ID
82
53
  ```
83
54
 
84
- Config: `~/.config/dunsocial/config.json` (0600). Override path with `DUN_CONFIG`.
85
-
86
- Env (wins over config):
55
+ ---
87
56
 
88
- | Var | Purpose |
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
- PATs with scoped UI management are a later follow-up. See `docs/cli.md` for a GitHub Actions announce example.
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
- ## Commands
99
-
100
- See `dun --help` for the full map. Highlights:
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
- 0 ok · 1 usage · 2 auth · 3 forbidden · 4 validation · 5 not found · 6 rate limited · 7 network
75
+ ---
124
76
 
125
- ## Agent skills
77
+ ## Useful commands
126
78
 
127
79
  ```bash
128
- dun skill install claude # ~/.claude/skills/dunsocial/SKILL.md
129
- dun skill install codex # ~/.codex/skills/... + AGENTS.md block
130
- ```
131
-
132
- ## Development
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
- ## Releases (npm)
87
+ Tips:
142
88
 
143
- Do **not** bump `version` by hand for normal releases.
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
- 1. Merge conventional commits affecting `apps/cli` to `main`
146
- 2. Merge the **release-please** PR (version + `CHANGELOG.md`)
147
- 3. CI publishes `dunsocial` to npm (`NPM_TOKEN` secret)
93
+ ---
148
94
 
149
- See [docs/release.md](../../docs/release.md). Manual: Actions → **Release CLI** → run with an existing tag.
95
+ ## Links
150
96
 
151
- ## Architecture
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
- Do not import DB/MCP server code into the CLI. Keep it a thin client.
102
+ MIT
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
- import{mkdir as dn,readFile as Ie,writeFile as rn,chmod as tn,unlink as mn}from"node:fs/promises";import{dirname as yn,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",W="0.2.0";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 M(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 F(e){return K(e,"help","h")}function q(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)}
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 Rn,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 Q="0.2.0";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 L(){return E()}async function J(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 X=`dun — DunSocial agent-first CLI v${Q}
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]??X}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)}
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 H(e,n,u){if(!e)return;if(u!==void 0){process.stderr.write(`[debug] ${n} ${JSON.stringify(u)}
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/${Q}`,...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);H(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(H(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 he(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 he(e,n){let u=$(n,"token")||process.env.DUN_TOKEN;if(u)return Fe(e,u);return qe(e,n)}async function Fe(e,n){let u={...e,token:n},y=await D(u).get("/api/user/profile");await J({...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:L(),method:"token",message:"Logged in. Next: dun workspace list && dun workspace use <id|slug>"},e)}async function qe(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(`
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 ae(e){let n=re(e),{group:u,action:t,nest:y,flags:d,positionals:i}=n;if(q(d)&&!u)return process.stdout.write(`${W}
142
- `),G.OK;if(u==="help"||!u&&F(d)){let m=u==="help"?t||i[0]:void 0;return process.stdout.write(m?`${f(m)}
143
- `:`${X}
144
- `),G.OK}if(!u)return process.stdout.write(`${X}
145
- `),G.OK;if(F(d))return process.stdout.write(`${f(u)}
146
- `),G.OK;let r=await de({apiUrl:M(d,"api-url","apiUrl"),token:M(d,"token"),workspace:M(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(q(d))return s({version:W},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:W},r);break;default:throw new S(`Unknown command "${u}". Run dun --help`,G.USAGE)}return G.OK}catch(m){return ie(m,r)}}var en=await ae(process.argv.slice(2));process.exit(en);
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.0",
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": "./dist/index.js",
26
- "dunsocial": "./dist/index.js"
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 `dun` binary (`@dunsocial/cli`). Prefer **`--json`** on every command so output is machine-parseable.
8
+ Use the **`dun`** binary from the **`dunsocial`** npm package.
9
9
 
10
- Remote hosts (ChatGPT web, Claude web) should keep using the DunSocial **MCP** server. This CLI is for **local agents, terminals, and CI**.
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 (opens browser device login)
23
+ # Interactive (browser approve at app.dunsocial.com/cli/authorize)
16
24
  dun auth login
17
25
 
18
- # CI / headless — prefer PAT from Settings → CLI (dun_pat_…)
19
- export DUN_TOKEN="$DUN_PAT" DUN_WORKSPACE_ID="$DUN_WORKSPACE_ID"
20
- # legacy full session also works:
21
- dun auth login --token "$DUN_TOKEN" --json
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 (highest priority for CI):
37
+ Env overrides config file `~/.config/dunsocial/config.json`.
29
38
 
30
- - `DUN_TOKEN`
31
- - `DUN_WORKSPACE_ID`
32
- - `DUN_API_URL` (default `https://api.dunsocial.com`)
39
+ PAT notes:
33
40
 
34
- Config file: `~/.config/dunsocial/config.json` (mode 0600).
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` — confirm auth
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 for agents." \
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
- Or absolute time:
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
- # use returned asset id:
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 "We never promise same-day enterprise onboarding." --json
87
- dun memory search --prompt "onboarding promises" --collections <id> --json
88
- ```
89
-
90
- ## Threads (X)
84
+ dun memory save --collection <id> --text "" --json
85
+ dun memory search --prompt "" --collections <id> --json
91
86
 
92
- ```bash
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` commands.
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
- On failure with `--json`:
108
+ Failure JSON:
116
109
 
117
110
  ```json
118
- { "ok": false, "error": { "code": "validation", "message": "...", "details": null } }
111
+ { "ok": false, "error": { "code": "validation", "message": "", "details": null } }
119
112
  ```
120
113
 
121
114
  ## Pitfalls
122
115
 
123
- - Workspace is required for almost every command after login (`workspace use` or `--workspace` / `DUN_WORKSPACE_ID`).
124
- - `--accounts` takes DunSocial **social account ids**, not handles.
125
- - Local files must be uploaded via `media upload` before referencing in posts (unless you already have an asset id / storage key).
126
- - Platform-specific metadata (Reddit/IG/Pinterest) can be passed as `--meta '{"reddit":{...}}'` JSON when needed; prefer dedicated product flows in the dashboard for complex cases.
127
- - MCP OAuth scopes and CLI bearer sessions are different paths — fixing one does not fix the other.
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