@jclvsh/dropspace 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +273 -0
- package/dist/cli-utils.d.ts +4 -0
- package/dist/cli-utils.js +31 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +31 -0
- package/dist/client.d.ts +11 -0
- package/dist/client.js +110 -0
- package/dist/commands/connections.d.ts +3 -0
- package/dist/commands/connections.js +23 -0
- package/dist/commands/dropspace.d.ts +3 -0
- package/dist/commands/dropspace.js +15 -0
- package/dist/commands/keys.d.ts +3 -0
- package/dist/commands/keys.js +70 -0
- package/dist/commands/launches.d.ts +3 -0
- package/dist/commands/launches.js +263 -0
- package/dist/commands/personas.d.ts +3 -0
- package/dist/commands/personas.js +116 -0
- package/dist/commands/usage.d.ts +3 -0
- package/dist/commands/usage.js +15 -0
- package/dist/commands/webhooks.d.ts +3 -0
- package/dist/commands/webhooks.js +108 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +25 -0
- package/dist/tools/connections.d.ts +3 -0
- package/dist/tools/connections.js +30 -0
- package/dist/tools/dropspace.d.ts +3 -0
- package/dist/tools/dropspace.js +21 -0
- package/dist/tools/keys.d.ts +3 -0
- package/dist/tools/keys.js +107 -0
- package/dist/tools/launches.d.ts +3 -0
- package/dist/tools/launches.js +492 -0
- package/dist/tools/personas.d.ts +3 -0
- package/dist/tools/personas.js +152 -0
- package/dist/tools/usage.d.ts +3 -0
- package/dist/tools/usage.js +21 -0
- package/dist/tools/webhooks.d.ts +3 -0
- package/dist/tools/webhooks.js +183 -0
- package/dist/types.d.ts +257 -0
- package/dist/types.js +1 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# @jclvsh/dropspace
|
|
2
|
+
|
|
3
|
+
MCP server and CLI for the [Dropspace](https://dropspace.dev) API. Lets AI agents (Claude Code, Cursor) and humans manage launches, personas, platform connections, API keys, and webhooks.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @jclvsh/dropspace
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or install globally:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g @jclvsh/dropspace
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Configuration
|
|
18
|
+
|
|
19
|
+
### Environment Variables
|
|
20
|
+
|
|
21
|
+
| Variable | Required | Description |
|
|
22
|
+
| ------------------- | -------- | --------------------------------------------------- |
|
|
23
|
+
| `DROPSPACE_API_KEY` | Yes | Your Dropspace API key |
|
|
24
|
+
| `DROPSPACE_API_URL` | No | API base URL (default: `https://api.dropspace.dev`) |
|
|
25
|
+
|
|
26
|
+
### Claude Code
|
|
27
|
+
|
|
28
|
+
Add to your `.claude/settings.json`:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"dropspace": {
|
|
34
|
+
"command": "npx",
|
|
35
|
+
"args": ["-y", "@jclvsh/dropspace"],
|
|
36
|
+
"env": {
|
|
37
|
+
"DROPSPACE_API_KEY": "ds_live_your_key_here"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Cursor
|
|
45
|
+
|
|
46
|
+
Add to your `.cursor/mcp.json`:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"mcpServers": {
|
|
51
|
+
"dropspace": {
|
|
52
|
+
"command": "npx",
|
|
53
|
+
"args": ["-y", "@jclvsh/dropspace"],
|
|
54
|
+
"env": {
|
|
55
|
+
"DROPSPACE_API_KEY": "ds_live_your_key_here"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Available Tools
|
|
63
|
+
|
|
64
|
+
### Launches
|
|
65
|
+
|
|
66
|
+
| Tool | Description |
|
|
67
|
+
| ---------------------- | -------------------------------------------------------------- |
|
|
68
|
+
| `list_launches` | List your launches with pagination |
|
|
69
|
+
| `create_launch` | Create a new launch with content, media, and account selection |
|
|
70
|
+
| `get_launch` | Get a launch by ID with posting status |
|
|
71
|
+
| `update_launch` | Update a launch's schedule, status, content, or media |
|
|
72
|
+
| `delete_launch` | Delete a launch |
|
|
73
|
+
| `publish_launch` | Publish a launch to all configured platforms |
|
|
74
|
+
| `retry_launch` | Retry failed platforms for a launch |
|
|
75
|
+
| `generate_content` | Generate AI content and video scripts for a launch |
|
|
76
|
+
| `retry_launch_content` | Retry AI content generation for failed platforms |
|
|
77
|
+
| `get_launch_analytics` | Get publishing analytics with per-post engagement metrics |
|
|
78
|
+
| `get_launch_status` | Get detailed posting status with per-platform logs |
|
|
79
|
+
|
|
80
|
+
#### `create_launch` Parameters
|
|
81
|
+
|
|
82
|
+
| Parameter | Type | Required | Description |
|
|
83
|
+
| ----------------------------- | ------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
84
|
+
| `title` | string | Yes | Launch title |
|
|
85
|
+
| `product_description` | string | Yes | Product description |
|
|
86
|
+
| `platforms` | string[] | Yes | Platforms to publish to (1-9) |
|
|
87
|
+
| `product_url` | string | No | Product URL |
|
|
88
|
+
| `scheduled_date` | string | No | ISO 8601 datetime (15+ min in the future) |
|
|
89
|
+
| `persona_id` | string | No | Persona ID for tone/style |
|
|
90
|
+
| `dropspace_platforms` | string[] | No | Platforms to post via official dropspace accounts |
|
|
91
|
+
| `media_mode` | string | No | `"images"` or `"video"` |
|
|
92
|
+
| `user_platform_accounts` | object | No | Map of platform key to token_id (UUID). Simple keys: `twitter`, `reddit`, `instagram`, `tiktok`. LinkedIn: `linkedin:personal` or `linkedin:organization:<org_id>`. Facebook: `facebook:page:<page_id>` |
|
|
93
|
+
| `platform_contents` | object | No | Per-platform content (mutually exclusive with `custom_content`) |
|
|
94
|
+
| `custom_content` | string \| string[] | No | Single content for all platforms, or array of tweets for twitter thread mode (mutually exclusive with `platform_contents`) |
|
|
95
|
+
| `custom_content_reddit_title` | string | No | Reddit title (required with `custom_content` + reddit) |
|
|
96
|
+
| `media` | object[] | No | Inline media, URL or base64 (1-10 items, mutually exclusive with `media_assets`) |
|
|
97
|
+
| `media_assets` | object[] | No | Pre-uploaded media references (mutually exclusive with `media`) |
|
|
98
|
+
| `media_attach_platforms` | string[] | No | Platforms to attach media to |
|
|
99
|
+
| `generate_ai_videos` | string[] | No | Auto-generate AI videos (`"instagram"`, `"tiktok"`) |
|
|
100
|
+
|
|
101
|
+
#### `update_launch` Parameters
|
|
102
|
+
|
|
103
|
+
| Parameter | Type | Required | Description |
|
|
104
|
+
| ------------------------ | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
105
|
+
| `id` | string | Yes | Launch ID |
|
|
106
|
+
| `scheduled_date` | string \| null | No | ISO 8601 datetime or null to clear |
|
|
107
|
+
| `status` | string | No | `"draft"`, `"manual"`, `"trigger"`, `"scheduled"`, `"cancelled"` |
|
|
108
|
+
| `platform_contents` | object | No | Per-platform content overrides |
|
|
109
|
+
| `dropspace_platforms` | string[] | No | Official dropspace account platforms |
|
|
110
|
+
| `user_platform_accounts` | object | No | Map of platform key to token_id (UUID). Simple keys: `twitter`, `reddit`, `instagram`, `tiktok`. LinkedIn: `linkedin:personal` or `linkedin:organization:<org_id>`. Facebook: `facebook:page:<page_id>` |
|
|
111
|
+
| `media` | object[] | No | Inline media (URL or base64, 1-10 items) |
|
|
112
|
+
| `media_assets` | object[] | No | Pre-uploaded media references |
|
|
113
|
+
| `media_attach_platforms` | string[] | No | Platforms to attach media to |
|
|
114
|
+
| `media_mode` | string | No | `"images"` or `"video"` |
|
|
115
|
+
|
|
116
|
+
#### `generate_content` Parameters
|
|
117
|
+
|
|
118
|
+
| Parameter | Type | Required | Description |
|
|
119
|
+
| ------------------------ | -------- | -------- | ------------------------------------------------------------------- |
|
|
120
|
+
| `id` | string | Yes | Launch ID |
|
|
121
|
+
| `platforms` | string[] | No | Platforms to generate for (defaults to all) |
|
|
122
|
+
| `generate_video_scripts` | string[] | No | Platforms to generate video scripts for (`"instagram"`, `"tiktok"`) |
|
|
123
|
+
|
|
124
|
+
### Personas
|
|
125
|
+
|
|
126
|
+
| Tool | Description |
|
|
127
|
+
| ----------------- | ------------------------------------------------ |
|
|
128
|
+
| `list_personas` | List your personas |
|
|
129
|
+
| `create_persona` | Create a new persona for content tone/style |
|
|
130
|
+
| `get_persona` | Get a persona by ID with analysis details |
|
|
131
|
+
| `update_persona` | Update a persona's name or writing samples |
|
|
132
|
+
| `delete_persona` | Delete a persona |
|
|
133
|
+
| `analyze_persona` | Trigger AI analysis of a persona's writing style |
|
|
134
|
+
|
|
135
|
+
#### `analyze_persona` Parameters
|
|
136
|
+
|
|
137
|
+
| Parameter | Type | Required | Description |
|
|
138
|
+
| ------------------------ | ------------- | -------- | ------------------------------------------ |
|
|
139
|
+
| `id` | string (UUID) | Yes | Persona ID |
|
|
140
|
+
| `platforms` | string[] | No | Specific platforms to analyze samples from |
|
|
141
|
+
| `include_custom_samples` | boolean | No | Include custom writing samples in analysis |
|
|
142
|
+
|
|
143
|
+
#### `update_persona` Parameters
|
|
144
|
+
|
|
145
|
+
| Parameter | Type | Required | Description |
|
|
146
|
+
| ------------------- | ------ | -------- | ----------------------------------- |
|
|
147
|
+
| `id` | string | Yes | Persona ID |
|
|
148
|
+
| `name` | string | No | New persona name (1-100 characters) |
|
|
149
|
+
| `custom_samples` | array | No | Custom writing samples (max 50) |
|
|
150
|
+
| `twitter_samples` | array | No | Twitter writing samples (max 50) |
|
|
151
|
+
| `reddit_samples` | array | No | Reddit writing samples (max 50) |
|
|
152
|
+
| `facebook_samples` | array | No | Facebook writing samples (max 50) |
|
|
153
|
+
| `instagram_samples` | array | No | Instagram writing samples (max 50) |
|
|
154
|
+
| `tiktok_samples` | array | No | TikTok writing samples (max 50) |
|
|
155
|
+
| `linkedin_samples` | array | No | LinkedIn writing samples (max 50) |
|
|
156
|
+
|
|
157
|
+
### Connections
|
|
158
|
+
|
|
159
|
+
| Tool | Description |
|
|
160
|
+
| ------------------ | ----------------------------------------- |
|
|
161
|
+
| `list_connections` | List your connected social media accounts |
|
|
162
|
+
|
|
163
|
+
### Posts
|
|
164
|
+
|
|
165
|
+
| Tool | Description |
|
|
166
|
+
| ------------------ | ------------------------------------------------------ |
|
|
167
|
+
| `delete_post` | Delete a single published post from its platform |
|
|
168
|
+
| `delete_all_posts` | Delete all published posts for a launch from platforms |
|
|
169
|
+
|
|
170
|
+
### API Keys
|
|
171
|
+
|
|
172
|
+
| Tool | Description |
|
|
173
|
+
| --------------------- | -------------------------------------------------- |
|
|
174
|
+
| `get_current_api_key` | Get the current API key's info (no scope required) |
|
|
175
|
+
| `list_api_keys` | List your API keys (requires admin scope) |
|
|
176
|
+
| `create_api_key` | Create a new API key (raw key returned once) |
|
|
177
|
+
| `rename_api_key` | Rename an API key |
|
|
178
|
+
| `revoke_api_key` | Permanently revoke an API key |
|
|
179
|
+
|
|
180
|
+
#### `create_api_key` Parameters
|
|
181
|
+
|
|
182
|
+
| Parameter | Type | Required | Description |
|
|
183
|
+
| --------- | -------- | -------- | ---------------------------------------------------------------------------------------------- |
|
|
184
|
+
| `name` | string | Yes | Key name (1-100 characters) |
|
|
185
|
+
| `scopes` | string[] | No | Permission scopes (defaults to all): `read`, `write`, `delete`, `publish`, `generate`, `admin` |
|
|
186
|
+
|
|
187
|
+
### Webhooks
|
|
188
|
+
|
|
189
|
+
| Tool | Description |
|
|
190
|
+
| ------------------------- | ------------------------------------------------------------ |
|
|
191
|
+
| `list_webhooks` | List your webhooks |
|
|
192
|
+
| `create_webhook` | Create a webhook endpoint (secret returned once) |
|
|
193
|
+
| `get_webhook` | Get a webhook by ID |
|
|
194
|
+
| `update_webhook` | Update a webhook's URL, events, or active status |
|
|
195
|
+
| `delete_webhook` | Delete a webhook |
|
|
196
|
+
| `rotate_webhook_secret` | Rotate a webhook's signing secret (new secret returned once) |
|
|
197
|
+
| `list_webhook_deliveries` | List delivery attempts for a webhook |
|
|
198
|
+
|
|
199
|
+
#### `create_webhook` Parameters
|
|
200
|
+
|
|
201
|
+
| Parameter | Type | Required | Description |
|
|
202
|
+
| --------- | -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
203
|
+
| `url` | string | Yes | HTTPS webhook URL |
|
|
204
|
+
| `events` | string[] | Yes | Events to subscribe to: `launch.completed`, `launch.failed`, `launch.partial`, `media.ready`, `persona.analyzed`, `post.deleted` |
|
|
205
|
+
|
|
206
|
+
#### `update_webhook` Parameters
|
|
207
|
+
|
|
208
|
+
| Parameter | Type | Required | Description |
|
|
209
|
+
| --------- | ------------- | -------- | ----------------------------- |
|
|
210
|
+
| `id` | string (UUID) | Yes | Webhook ID |
|
|
211
|
+
| `url` | string | No | New HTTPS webhook URL |
|
|
212
|
+
| `events` | string[] | No | New events to subscribe to |
|
|
213
|
+
| `active` | boolean | No | Enable or disable the webhook |
|
|
214
|
+
|
|
215
|
+
### Dropspace
|
|
216
|
+
|
|
217
|
+
| Tool | Description |
|
|
218
|
+
| ---------------------- | -------------------------------------------------------- |
|
|
219
|
+
| `get_dropspace_status` | Check which platforms have a connected dropspace account |
|
|
220
|
+
|
|
221
|
+
### Usage
|
|
222
|
+
|
|
223
|
+
| Tool | Description |
|
|
224
|
+
| ----------- | ------------------------------------------------------- |
|
|
225
|
+
| `get_usage` | Get your current plan, usage limits, and billing period |
|
|
226
|
+
|
|
227
|
+
## API Key & Scopes
|
|
228
|
+
|
|
229
|
+
Generate an API key at [dropspace.dev/settings](https://dropspace.dev/settings) under the API section.
|
|
230
|
+
|
|
231
|
+
API keys support scoped permissions:
|
|
232
|
+
|
|
233
|
+
| Scope | Access |
|
|
234
|
+
| ---------- | ---------------------------------------------------------------------- |
|
|
235
|
+
| `read` | List and read launches, personas, connections, dropspace status |
|
|
236
|
+
| `write` | Create and update launches, personas |
|
|
237
|
+
| `delete` | Delete launches, personas |
|
|
238
|
+
| `publish` | Publish and retry launches |
|
|
239
|
+
| `generate` | Generate AI content, retry content generation |
|
|
240
|
+
| `admin` | Manage API keys and webhooks |
|
|
241
|
+
|
|
242
|
+
### Recommended Scopes by Use Case
|
|
243
|
+
|
|
244
|
+
| Use Case | Recommended Scopes |
|
|
245
|
+
| ---------------- | --------------------------------------------------------- |
|
|
246
|
+
| AI agents / MCP | `read`, `write`, `generate` |
|
|
247
|
+
| CI/CD publishing | `read`, `publish` |
|
|
248
|
+
| Monitoring | `read` |
|
|
249
|
+
| Full management | `read`, `write`, `delete`, `publish`, `generate`, `admin` |
|
|
250
|
+
|
|
251
|
+
## Webhook Events
|
|
252
|
+
|
|
253
|
+
| Event | Description |
|
|
254
|
+
| ------------------ | ------------------------------------------------------------ |
|
|
255
|
+
| `launch.completed` | All platforms finished posting successfully |
|
|
256
|
+
| `launch.failed` | Launch posting failed on all platforms |
|
|
257
|
+
| `launch.partial` | Launch posting succeeded on some platforms, failed on others |
|
|
258
|
+
| `media.ready` | Media generation job completed |
|
|
259
|
+
| `persona.analyzed` | Persona AI analysis completed |
|
|
260
|
+
| `post.deleted` | A published post was deleted from its platform |
|
|
261
|
+
|
|
262
|
+
## Development
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
# Install dependencies
|
|
266
|
+
npm install
|
|
267
|
+
|
|
268
|
+
# Build
|
|
269
|
+
npm run build
|
|
270
|
+
|
|
271
|
+
# Run
|
|
272
|
+
DROPSPACE_API_KEY=ds_live_... npm start
|
|
273
|
+
```
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function formatOutput(data: unknown, format?: string): void;
|
|
2
|
+
export declare function handleError(error: unknown): never;
|
|
3
|
+
export declare function getFormat(opts: Record<string, unknown>): string;
|
|
4
|
+
export declare function parseList(value: string): string[];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function formatOutput(data, format = "json") {
|
|
2
|
+
if (format === "table" && typeof data === "object" && data !== null) {
|
|
3
|
+
const obj = data;
|
|
4
|
+
if (Array.isArray(obj.data)) {
|
|
5
|
+
console.table(obj.data);
|
|
6
|
+
if (obj.pagination) {
|
|
7
|
+
const p = obj.pagination;
|
|
8
|
+
console.error(`\npage ${p.page}/${p.total_pages} (${p.total} total)`);
|
|
9
|
+
}
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (obj.data && typeof obj.data === "object") {
|
|
13
|
+
console.log(JSON.stringify(obj.data, null, 2));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
console.log(JSON.stringify(data, null, 2));
|
|
18
|
+
}
|
|
19
|
+
export function handleError(error) {
|
|
20
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
21
|
+
console.error(`error: ${message}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
export function getFormat(opts) {
|
|
25
|
+
if (opts.json)
|
|
26
|
+
return "json";
|
|
27
|
+
return opts.format || "json";
|
|
28
|
+
}
|
|
29
|
+
export function parseList(value) {
|
|
30
|
+
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
31
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { DropspaceClient } from "./client.js";
|
|
4
|
+
import { handleError } from "./cli-utils.js";
|
|
5
|
+
import { registerLaunchCommands } from "./commands/launches.js";
|
|
6
|
+
import { registerPersonaCommands } from "./commands/personas.js";
|
|
7
|
+
import { registerConnectionCommands } from "./commands/connections.js";
|
|
8
|
+
import { registerKeyCommands } from "./commands/keys.js";
|
|
9
|
+
import { registerWebhookCommands } from "./commands/webhooks.js";
|
|
10
|
+
import { registerDropspaceCommands } from "./commands/dropspace.js";
|
|
11
|
+
import { registerUsageCommands } from "./commands/usage.js";
|
|
12
|
+
const program = new Command()
|
|
13
|
+
.name("dropspace")
|
|
14
|
+
.description("dropspace CLI - manage launches, personas, and more")
|
|
15
|
+
.version("2.0.0")
|
|
16
|
+
.option("--json", "output as JSON (default)")
|
|
17
|
+
.option("--format <fmt>", "output format: json, table", "json");
|
|
18
|
+
try {
|
|
19
|
+
const client = new DropspaceClient();
|
|
20
|
+
registerLaunchCommands(program, client);
|
|
21
|
+
registerPersonaCommands(program, client);
|
|
22
|
+
registerConnectionCommands(program, client);
|
|
23
|
+
registerKeyCommands(program, client);
|
|
24
|
+
registerWebhookCommands(program, client);
|
|
25
|
+
registerDropspaceCommands(program, client);
|
|
26
|
+
registerUsageCommands(program, client);
|
|
27
|
+
await program.parseAsync(process.argv);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
handleError(error);
|
|
31
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class DropspaceClient {
|
|
2
|
+
private apiKey;
|
|
3
|
+
private baseUrl;
|
|
4
|
+
constructor();
|
|
5
|
+
get<T>(path: string, params?: Record<string, string>): Promise<T>;
|
|
6
|
+
post<T>(path: string, body?: unknown): Promise<T>;
|
|
7
|
+
patch<T>(path: string, body: unknown): Promise<T>;
|
|
8
|
+
delete(path: string): Promise<void>;
|
|
9
|
+
private request;
|
|
10
|
+
private handleError;
|
|
11
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
2
|
+
export class DropspaceClient {
|
|
3
|
+
apiKey;
|
|
4
|
+
baseUrl;
|
|
5
|
+
constructor() {
|
|
6
|
+
const apiKey = process.env.DROPSPACE_API_KEY;
|
|
7
|
+
if (!apiKey) {
|
|
8
|
+
throw new Error("DROPSPACE_API_KEY environment variable is required");
|
|
9
|
+
}
|
|
10
|
+
if (!apiKey.startsWith("ds_live_")) {
|
|
11
|
+
throw new Error("DROPSPACE_API_KEY must start with 'ds_live_'. Get one at https://dropspace.dev/settings");
|
|
12
|
+
}
|
|
13
|
+
this.apiKey = apiKey;
|
|
14
|
+
this.baseUrl = (process.env.DROPSPACE_API_URL || "https://api.dropspace.dev").replace(/\/$/, "");
|
|
15
|
+
}
|
|
16
|
+
async get(path, params) {
|
|
17
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
18
|
+
if (params) {
|
|
19
|
+
for (const [key, value] of Object.entries(params)) {
|
|
20
|
+
if (value !== undefined && value !== "") {
|
|
21
|
+
url.searchParams.set(key, value);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return this.request(url.toString(), { method: "GET" });
|
|
26
|
+
}
|
|
27
|
+
async post(path, body) {
|
|
28
|
+
return this.request(`${this.baseUrl}${path}`, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: { "Content-Type": "application/json" },
|
|
31
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async patch(path, body) {
|
|
35
|
+
return this.request(`${this.baseUrl}${path}`, {
|
|
36
|
+
method: "PATCH",
|
|
37
|
+
headers: { "Content-Type": "application/json" },
|
|
38
|
+
body: JSON.stringify(body),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async delete(path) {
|
|
42
|
+
try {
|
|
43
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
44
|
+
method: "DELETE",
|
|
45
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
46
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
47
|
+
});
|
|
48
|
+
if (!response.ok && response.status !== 204) {
|
|
49
|
+
await this.handleError(response);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (error instanceof Error && error.name === "TimeoutError") {
|
|
54
|
+
throw new Error("request timed out after 30s");
|
|
55
|
+
}
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async request(url, init) {
|
|
60
|
+
let response;
|
|
61
|
+
try {
|
|
62
|
+
response = await fetch(url, {
|
|
63
|
+
...init,
|
|
64
|
+
headers: {
|
|
65
|
+
...(init.headers || {}),
|
|
66
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
67
|
+
},
|
|
68
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (error instanceof Error && error.name === "TimeoutError") {
|
|
73
|
+
throw new Error("request timed out after 30s");
|
|
74
|
+
}
|
|
75
|
+
if (error instanceof TypeError) {
|
|
76
|
+
throw new Error(`network error: ${error.message}. check DROPSPACE_API_URL and your connection`);
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
await this.handleError(response);
|
|
82
|
+
}
|
|
83
|
+
return (await response.json());
|
|
84
|
+
}
|
|
85
|
+
async handleError(response) {
|
|
86
|
+
let errorMessage;
|
|
87
|
+
try {
|
|
88
|
+
const body = (await response.json());
|
|
89
|
+
if (body.error) {
|
|
90
|
+
errorMessage = `[${body.error.code}] ${body.error.message}`;
|
|
91
|
+
if (body.error.field) {
|
|
92
|
+
errorMessage += ` (field: ${body.error.field})`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
errorMessage = `API error: ${response.status} ${response.statusText}`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
errorMessage = `API error: ${response.status} ${response.statusText}`;
|
|
101
|
+
}
|
|
102
|
+
if (response.status === 429) {
|
|
103
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
104
|
+
if (retryAfter) {
|
|
105
|
+
errorMessage += ` — retry after ${retryAfter}s`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
throw new Error(errorMessage);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { formatOutput, handleError, getFormat } from "../cli-utils.js";
|
|
2
|
+
export function registerConnectionCommands(program, client) {
|
|
3
|
+
const connections = program.command("connections").alias("c").description("manage platform connections");
|
|
4
|
+
connections
|
|
5
|
+
.command("list")
|
|
6
|
+
.description("list connected social media accounts")
|
|
7
|
+
.option("-p, --page <n>", "page number", "1")
|
|
8
|
+
.option("-s, --page-size <n>", "items per page (max 100)", "50")
|
|
9
|
+
.action(async (opts) => {
|
|
10
|
+
try {
|
|
11
|
+
const params = {};
|
|
12
|
+
if (opts.page)
|
|
13
|
+
params.page = opts.page;
|
|
14
|
+
if (opts.pageSize)
|
|
15
|
+
params.page_size = opts.pageSize;
|
|
16
|
+
const result = await client.get("/connections", params);
|
|
17
|
+
formatOutput(result, getFormat(program.opts()));
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
handleError(error);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { formatOutput, handleError, getFormat } from "../cli-utils.js";
|
|
2
|
+
export function registerDropspaceCommands(program, client) {
|
|
3
|
+
program
|
|
4
|
+
.command("status")
|
|
5
|
+
.description("check dropspace official account platform connections")
|
|
6
|
+
.action(async () => {
|
|
7
|
+
try {
|
|
8
|
+
const result = await client.get("/dropspace/status");
|
|
9
|
+
formatOutput(result, getFormat(program.opts()));
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
handleError(error);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { formatOutput, handleError, getFormat, parseList } from "../cli-utils.js";
|
|
2
|
+
export function registerKeyCommands(program, client) {
|
|
3
|
+
const keys = program.command("keys").alias("k").description("manage API keys");
|
|
4
|
+
keys
|
|
5
|
+
.command("me")
|
|
6
|
+
.description("get current API key info")
|
|
7
|
+
.action(async () => {
|
|
8
|
+
try {
|
|
9
|
+
const result = await client.get("/keys/me");
|
|
10
|
+
formatOutput(result, getFormat(program.opts()));
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
handleError(error);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
keys
|
|
17
|
+
.command("list")
|
|
18
|
+
.description("list API keys (requires admin scope)")
|
|
19
|
+
.action(async () => {
|
|
20
|
+
try {
|
|
21
|
+
const result = await client.get("/keys");
|
|
22
|
+
formatOutput(result, getFormat(program.opts()));
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
handleError(error);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
keys
|
|
29
|
+
.command("create")
|
|
30
|
+
.description("create a new API key (requires admin scope)")
|
|
31
|
+
.requiredOption("-n, --name <name>", "key name")
|
|
32
|
+
.option("--scopes <list>", "comma-separated scopes: read,write,delete,publish,generate,admin", parseList)
|
|
33
|
+
.action(async (opts) => {
|
|
34
|
+
try {
|
|
35
|
+
const body = { name: opts.name };
|
|
36
|
+
if (opts.scopes)
|
|
37
|
+
body.scopes = opts.scopes;
|
|
38
|
+
const result = await client.post("/keys", body);
|
|
39
|
+
formatOutput(result, getFormat(program.opts()));
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
handleError(error);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
keys
|
|
46
|
+
.command("rename <id>")
|
|
47
|
+
.description("rename an API key (requires admin scope)")
|
|
48
|
+
.requiredOption("-n, --name <name>", "new key name")
|
|
49
|
+
.action(async (id, opts) => {
|
|
50
|
+
try {
|
|
51
|
+
const result = await client.patch(`/keys/${id}`, { name: opts.name });
|
|
52
|
+
formatOutput(result, getFormat(program.opts()));
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
handleError(error);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
keys
|
|
59
|
+
.command("revoke <id>")
|
|
60
|
+
.description("permanently revoke an API key (requires admin scope)")
|
|
61
|
+
.action(async (id) => {
|
|
62
|
+
try {
|
|
63
|
+
await client.delete(`/keys/${id}`);
|
|
64
|
+
console.log("api key revoked");
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
handleError(error);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|