clipugc 0.2.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 +249 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +31 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/account.d.ts +12 -0
- package/dist/commands/account.js +48 -0
- package/dist/commands/account.js.map +1 -0
- package/dist/commands/auth.d.ts +14 -0
- package/dist/commands/auth.js +152 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/characters.d.ts +3 -0
- package/dist/commands/characters.js +261 -0
- package/dist/commands/characters.js.map +1 -0
- package/dist/commands/config.d.ts +5 -0
- package/dist/commands/config.js +69 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/credits.d.ts +2 -0
- package/dist/commands/credits.js +117 -0
- package/dist/commands/credits.js.map +1 -0
- package/dist/commands/hooks.d.ts +2 -0
- package/dist/commands/hooks.js +32 -0
- package/dist/commands/hooks.js.map +1 -0
- package/dist/commands/images.d.ts +3 -0
- package/dist/commands/images.js +257 -0
- package/dist/commands/images.js.map +1 -0
- package/dist/commands/videos.d.ts +2 -0
- package/dist/commands/videos.js +272 -0
- package/dist/commands/videos.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/services/api.d.ts +40 -0
- package/dist/services/api.js +139 -0
- package/dist/services/api.js.map +1 -0
- package/dist/services/characters.service.d.ts +135 -0
- package/dist/services/characters.service.js +276 -0
- package/dist/services/characters.service.js.map +1 -0
- package/dist/services/hooks.service.d.ts +9 -0
- package/dist/services/hooks.service.js +23 -0
- package/dist/services/hooks.service.js.map +1 -0
- package/dist/services/images.service.d.ts +95 -0
- package/dist/services/images.service.js +191 -0
- package/dist/services/images.service.js.map +1 -0
- package/dist/services/upload.service.d.ts +46 -0
- package/dist/services/upload.service.js +134 -0
- package/dist/services/upload.service.js.map +1 -0
- package/dist/services/user.service.d.ts +80 -0
- package/dist/services/user.service.js +35 -0
- package/dist/services/user.service.js.map +1 -0
- package/dist/services/videos.service.d.ts +100 -0
- package/dist/services/videos.service.js +187 -0
- package/dist/services/videos.service.js.map +1 -0
- package/dist/types/index.d.ts +47 -0
- package/dist/types/index.js +18 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/config.d.ts +18 -0
- package/dist/utils/config.js +65 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/errors.d.ts +52 -0
- package/dist/utils/errors.js +74 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.d.ts +11 -0
- package/dist/utils/logger.js +28 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/output.d.ts +16 -0
- package/dist/utils/output.js +53 -0
- package/dist/utils/output.js.map +1 -0
- package/dist/utils/poll.d.ts +37 -0
- package/dist/utils/poll.js +94 -0
- package/dist/utils/poll.js.map +1 -0
- package/dist/utils/prompt.d.ts +4 -0
- package/dist/utils/prompt.js +55 -0
- package/dist/utils/prompt.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +12 -0
- package/dist/version.js.map +1 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# ClipUGC CLI
|
|
2
|
+
|
|
3
|
+
Official command-line interface for [ClipUGC](https://clipugc.com) — create AI characters, generate photorealistic looks, and produce UGC-style marketing videos for your mobile app straight from the terminal.
|
|
4
|
+
|
|
5
|
+
ClipUGC makes AI-generated, influencer-style UGC (user-generated-content) ads for mobile apps. The workflow:
|
|
6
|
+
|
|
7
|
+
1. **Create an AI character** — a structured appearance "DNA" (age, gender, nationality, vibe, hair, eyes, skin tone, body type…).
|
|
8
|
+
2. **Generate looks** — photorealistic reference images of the character (different shots, templates, and scenes).
|
|
9
|
+
3. **Create video clips** — turn a selected look (or your own photo) into short talking/moving clips.
|
|
10
|
+
4. **Merge into a UGC ad** — combine a clip with your app's screen recording and a hook text (plus optional music) into a final, ready-to-post UGC ad.
|
|
11
|
+
|
|
12
|
+
Credits are consumed server-side per action: **image = 2**, **clip (5s) = 7**, **clip (10s) = 13**, **motion control = 3 per second** of driver video (rounded up, capped at 30s), **scene-staged clip (`--scene`) = image + clip (9 for 5s, 15 for 10s)**, **merge = 1**. Charges are duration-aware, and refunds return the exact amount charged. Check your balance and the live cost of each action anytime with `clipugc credits`, and your ledger with `clipugc credits history`.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
Requires **Node.js >= 20**.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g clipugc
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Then authenticate with an API key. Create one in the [ClipUGC dashboard](https://clipugc.com/dashboard) (API keys section):
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
clipugc auth login
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quickstart
|
|
29
|
+
|
|
30
|
+
End-to-end: character → looks → clip → final UGC ad.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# 1. Authenticate (prompts for your API key)
|
|
34
|
+
clipugc auth login
|
|
35
|
+
|
|
36
|
+
# 2. Check your credit balance and per-action costs
|
|
37
|
+
clipugc credits
|
|
38
|
+
|
|
39
|
+
# 3. Create an AI character
|
|
40
|
+
clipugc characters create --description "casual gen-z woman in her early 20s, brown hair, friendly smile"
|
|
41
|
+
|
|
42
|
+
# 4. Generate reference looks for the character (waits until done)
|
|
43
|
+
clipugc images generate --character <characterId> --shots frontal,three_quarter --wait
|
|
44
|
+
|
|
45
|
+
# 5. Pick the look you like best
|
|
46
|
+
clipugc images list --character <characterId>
|
|
47
|
+
|
|
48
|
+
# 6. Turn the selected look into a short video clip
|
|
49
|
+
clipugc videos create --image <imageId> --prompt "excitedly talking to camera about a new app" --wait
|
|
50
|
+
|
|
51
|
+
# 7. Merge the clip with your app's screen recording + a hook
|
|
52
|
+
clipugc videos merge <videoId> --app-video ./screenrec.mp4 --hook "This app fixed my morning routine" --wait
|
|
53
|
+
|
|
54
|
+
# 8. Download the final UGC ad
|
|
55
|
+
clipugc videos download <videoId> -o ugc-ad.mp4
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Tip: add `--json` to any command to get raw JSON (handy for grabbing ids in scripts), and `--wait` to any generation command to poll with a spinner until it completes.
|
|
59
|
+
|
|
60
|
+
## Commands
|
|
61
|
+
|
|
62
|
+
### Auth
|
|
63
|
+
|
|
64
|
+
| Command | Description |
|
|
65
|
+
|---------|-------------|
|
|
66
|
+
| `clipugc auth login [--api-key <key>]` | Authenticate the CLI. Prompts securely for the key if `--api-key` is omitted, validates it against the API, and stores it. |
|
|
67
|
+
| `clipugc auth status` | Show whether you are logged in and as whom. |
|
|
68
|
+
| `clipugc auth logout` | Remove the stored API key. |
|
|
69
|
+
| `clipugc whoami` | Show the authenticated account. |
|
|
70
|
+
|
|
71
|
+
### Config
|
|
72
|
+
|
|
73
|
+
| Command | Description |
|
|
74
|
+
|---------|-------------|
|
|
75
|
+
| `clipugc config list` | Show all config values. |
|
|
76
|
+
| `clipugc config get <key>` | Read one config value. |
|
|
77
|
+
| `clipugc config set <key> <value>` | Set a config value (`apiBaseUrl`, `apiKey`, `email`). |
|
|
78
|
+
| `clipugc config path` | Print the config file path. |
|
|
79
|
+
|
|
80
|
+
### Credits and account
|
|
81
|
+
|
|
82
|
+
| Command | Description |
|
|
83
|
+
|---------|-------------|
|
|
84
|
+
| `clipugc credits` | Show credit balance and per-action costs. |
|
|
85
|
+
| `clipugc credits history [--per-page <n>] [--page <n>]` | Show your credit transaction history (spends, top-ups, refunds) as a paginated table. Add `--json` for scripting. |
|
|
86
|
+
| `clipugc credits packs` | List purchasable credit packs (buy on the web dashboard / mobile app). |
|
|
87
|
+
| `clipugc account delete [--yes]` | Permanently delete your account. Double confirmation (y/N, then type `DELETE`); `--yes` skips both. |
|
|
88
|
+
|
|
89
|
+
### Characters
|
|
90
|
+
|
|
91
|
+
| Command | Description |
|
|
92
|
+
|---------|-------------|
|
|
93
|
+
| `clipugc characters list [--discover\|--mine\|--feed] [--search <q>] [--page N] [--per-page N]` | List characters. `--discover` shows public characters, `--mine` only yours, `--feed` combines both (your characters newest-first, then public ones in unlock order — a Locked column marks locked rows). `--per-page` max 50. |
|
|
94
|
+
| `clipugc characters create --description "…" [--scene "…"] [--inspiration f1.jpg…] [--private] [--make-video [--motion-prompt "…"]] [--wait]` | Create a character from a plain-text description — the first look generates automatically (2 credits). Public by default. `--make-video` also stages the character's first video clip (its id + status are printed for `videos` follow-ups); `--motion-prompt` steers that clip's motion. |
|
|
95
|
+
| `clipugc characters show <id>` | Show a character's details and DNA, plus its display name, video/picture counts, and preview clip when available. |
|
|
96
|
+
| `clipugc characters rename <id> --name "New"` | Rename a character. |
|
|
97
|
+
| `clipugc characters publish <id>` / `unpublish <id>` | Make a character public / private. |
|
|
98
|
+
| `clipugc characters delete <id> [--yes]` | Delete a character (`--yes` skips the confirmation). |
|
|
99
|
+
|
|
100
|
+
`characters create` advanced flags (structured DNA path, rarely needed — use `--description` instead):
|
|
101
|
+
|
|
102
|
+
| Flag | Values |
|
|
103
|
+
|------|--------|
|
|
104
|
+
| `--name "Full Name"` | Required. 2–120 chars. |
|
|
105
|
+
| `--age` | 18–99 |
|
|
106
|
+
| `--gender` | e.g. `male` \| `female` \| `other` |
|
|
107
|
+
| `--dna-json <file-or-inline-json>` | Appearance DNA fields (nationality, vibe, hair/eye color, …) as a JSON file path or inline JSON object. |
|
|
108
|
+
|
|
109
|
+
### Images (looks)
|
|
110
|
+
|
|
111
|
+
| Command | Description |
|
|
112
|
+
|---------|-------------|
|
|
113
|
+
| `clipugc images generate --character <id> [flags] [--wait]` | Generate reference images (looks) for a character. Costs 2 credits per image. |
|
|
114
|
+
| `clipugc images list --character <id>` | List a character's images. |
|
|
115
|
+
| `clipugc images show <id>` | Show image details. |
|
|
116
|
+
| `clipugc images download <id> [-o out.png]` | Download the look image to disk (`-o` creates missing parent directories). |
|
|
117
|
+
| `clipugc images status <id>` | Check generation status. |
|
|
118
|
+
| `clipugc images variation <id> --scene "..." [--count 1-4] [--before-after] [--wait]` | Generate scene variations of an existing image. |
|
|
119
|
+
| `clipugc images retry <id> [--wait]` | Retry a failed generation. |
|
|
120
|
+
| `clipugc images delete <id> [--yes]` | Delete an image. |
|
|
121
|
+
|
|
122
|
+
`images generate` flags:
|
|
123
|
+
|
|
124
|
+
| Flag | Values |
|
|
125
|
+
|------|--------|
|
|
126
|
+
| `--shots` | Comma-separated: `frontal`, `three_quarter`, `profile`, `back` |
|
|
127
|
+
| `--template` | `model_digitals` \| `scene_recreation` \| `specific_angle` |
|
|
128
|
+
| `--scene "..."` | Scene description, max 600 chars. |
|
|
129
|
+
| `--resolution` | `0.5K` \| `1K` \| `2K` \| `4K` |
|
|
130
|
+
| `--wait` | Poll with a spinner until completed or failed. |
|
|
131
|
+
|
|
132
|
+
### Videos
|
|
133
|
+
|
|
134
|
+
| Command | Description |
|
|
135
|
+
|---------|-------------|
|
|
136
|
+
| `clipugc videos list [--character <id>] [--mergeable \| --finals] [--page N] [--per-page N]` | List your videos. `--character` filters to one AI character, `--mergeable` shows only completed clips not yet merged (ready for `videos merge`), `--finals` shows only merged final UGC videos. |
|
|
137
|
+
| `clipugc videos create (--image <lookId> \| --photo <file>) [flags] [--wait]` | Create a video clip from a look or your own photo. Costs 7 credits (5s) or 13 (10s); a `--scene` staged clip costs 9. |
|
|
138
|
+
| `clipugc videos motion (--image <lookId> \| --photo <file>) --driver <video.mp4> [--keep-sound] [--wait]` | Animate a look/photo using a driver video (mp4/mov, max 50MB, max 30s). Costs 3 credits per second of driver video (rounded up, capped at 30s). |
|
|
139
|
+
| `clipugc videos merge <videoId> --app-video <screenrec.mp4> --hook "..." [--music <file.mp3>] [--wait]` | Merge a clip with your app's screen recording + a hook (max 150 chars) into the final UGC ad. Costs 1 credit. `--wait` blocks until the merge render finishes (or fails, refunding the credit). |
|
|
140
|
+
| `clipugc videos show <id>` | Show video details. |
|
|
141
|
+
| `clipugc videos status <id>` | Check generation status. |
|
|
142
|
+
| `clipugc videos download <id> [-o out.mp4]` | Download the finished video (`-o` creates missing parent directories). |
|
|
143
|
+
| `clipugc videos retry <id> [--wait]` | Retry a failed generation. |
|
|
144
|
+
| `clipugc videos delete <id> [--yes]` | Delete a video. |
|
|
145
|
+
|
|
146
|
+
`videos create` flags:
|
|
147
|
+
|
|
148
|
+
| Flag | Values |
|
|
149
|
+
|------|--------|
|
|
150
|
+
| `--image <lookId>` or `--photo <file>` | One of the two is required. Photo: png/jpg/jpeg/webp. |
|
|
151
|
+
| `--prompt "..."` | Video prompt, max 1500 chars. |
|
|
152
|
+
| `--scene "..."` | Scene description, max 600 chars. Makes it a scene-staged clip (image + clip: 9 credits at 5s, 15 at 10s). |
|
|
153
|
+
| `--duration` | `5` \| `10` (seconds). 5s = 7 credits, 10s = 13. |
|
|
154
|
+
| `--keep-sound` | Keep the generated audio. |
|
|
155
|
+
| `--wait` | Poll until completed or failed. |
|
|
156
|
+
|
|
157
|
+
File uploads are handled automatically via presigned URLs. Accepted formats by purpose: photo `png/jpg/jpeg/webp`; app video and driver video `mp4/mov`; music `mp3/wav/m4a`.
|
|
158
|
+
|
|
159
|
+
### Hooks
|
|
160
|
+
|
|
161
|
+
| Command | Description |
|
|
162
|
+
|---------|-------------|
|
|
163
|
+
| `clipugc hooks suggest [--context "my app is a habit tracker"]` | Get AI-suggested hook texts for your UGC ad. |
|
|
164
|
+
|
|
165
|
+
## Configuration
|
|
166
|
+
|
|
167
|
+
### Config file
|
|
168
|
+
|
|
169
|
+
Settings are stored at `~/.config/clipugc/config.json`:
|
|
170
|
+
|
|
171
|
+
| Key | Description |
|
|
172
|
+
|-----|-------------|
|
|
173
|
+
| `apiBaseUrl` | ClipUGC API base URL. |
|
|
174
|
+
| `apiKey` | Your API key (set by `clipugc auth login`). |
|
|
175
|
+
| `email` | Account email (set on login). |
|
|
176
|
+
|
|
177
|
+
Manage with `clipugc config list / get / set / path`.
|
|
178
|
+
|
|
179
|
+
### Environment variables
|
|
180
|
+
|
|
181
|
+
Env vars override the config file:
|
|
182
|
+
|
|
183
|
+
| Variable | Overrides |
|
|
184
|
+
|----------|-----------|
|
|
185
|
+
| `CLIPUGC_API_KEY` | `apiKey` |
|
|
186
|
+
| `CLIPUGC_API_BASE_URL` | `apiBaseUrl` |
|
|
187
|
+
|
|
188
|
+
### JSON output
|
|
189
|
+
|
|
190
|
+
Every command accepts the global `--json` flag to print raw JSON instead of formatted output — use it for scripting and to capture ids:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
clipugc characters list --mine --json
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Waiting on long-running jobs
|
|
197
|
+
|
|
198
|
+
Generation commands (`images generate`, `images variation`, `images retry`, `videos create`, `videos motion`, `videos merge`, `videos retry`) accept `--wait` to poll with a spinner until the job is `completed` or `failed`. Without `--wait`, the command returns immediately and you can poll with `images status <id>` / `videos status <id>`.
|
|
199
|
+
|
|
200
|
+
### Exit codes
|
|
201
|
+
|
|
202
|
+
| Code | Meaning |
|
|
203
|
+
|------|---------|
|
|
204
|
+
| 0 | Success |
|
|
205
|
+
| 1 | Generic error |
|
|
206
|
+
| 2 | Validation error |
|
|
207
|
+
| 3 | Authentication error |
|
|
208
|
+
| 4 | Not found |
|
|
209
|
+
| 5 | Premium required |
|
|
210
|
+
| 6 | Insufficient credits |
|
|
211
|
+
| 7 | Network error / server unreachable |
|
|
212
|
+
|
|
213
|
+
## Claude Code Skills
|
|
214
|
+
|
|
215
|
+
If you use [Claude Code](https://claude.ai/code), this repo ships two skills:
|
|
216
|
+
|
|
217
|
+
- **`clipugc`** — drive the whole CLI through natural language, with automatic prerequisite checks (CLI installed, logged in, enough credits) and guided end-to-end workflows.
|
|
218
|
+
- **`ugc-director`** — a TikTok UGC ad director: turns an app idea into a complete creative plan (silent-reaction archetype, hook text, casting, copy-paste look & video prompts) and executes it with the CLI. Built on research into what actually converts: mouth-closed reaction formats (no lip-sync = no AI uncanny valley), text-overlay hooks, a 12-archetype reaction taxonomy, hook formula library, and a casting matrix by app category.
|
|
219
|
+
|
|
220
|
+
**Install** via the Claude Code plugin system:
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
/plugin marketplace add mirzemehdi/ClipUGC-CLI
|
|
224
|
+
/plugin install clipugc@ClipUGC-CLI
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Or clone this repo and Claude Code picks up the skills from `.claude/skills/` when working inside it.
|
|
228
|
+
|
|
229
|
+
**Use:**
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
/ugc-director make a TikTok ad for my habit tracker app
|
|
233
|
+
/clipugc create a character for my fitness app ads
|
|
234
|
+
/clipugc make a UGC ad from my screen recording
|
|
235
|
+
/clipugc how many credits do I have left
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Local development
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
npm ci
|
|
242
|
+
npm run build # compile TypeScript to dist/
|
|
243
|
+
npm test # run the vitest suite
|
|
244
|
+
npm run dev # run from source (tsx src/index.ts)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## License
|
|
248
|
+
|
|
249
|
+
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { getVersion } from './version.js';
|
|
3
|
+
import { registerAuthCommands } from './commands/auth.js';
|
|
4
|
+
import { registerConfigCommands } from './commands/config.js';
|
|
5
|
+
import { registerCreditsCommands } from './commands/credits.js';
|
|
6
|
+
import { registerAccountCommands } from './commands/account.js';
|
|
7
|
+
import { registerCharactersCommands } from './commands/characters.js';
|
|
8
|
+
import { registerImagesCommands } from './commands/images.js';
|
|
9
|
+
import { registerVideosCommands } from './commands/videos.js';
|
|
10
|
+
import { registerHooksCommands } from './commands/hooks.js';
|
|
11
|
+
export function createCli() {
|
|
12
|
+
const program = new Command();
|
|
13
|
+
program
|
|
14
|
+
.name('clipugc')
|
|
15
|
+
.description('ClipUGC CLI — create AI characters, generate looks, and produce UGC-style videos for your mobile app.\n' +
|
|
16
|
+
'Get an API key from your dashboard at https://clipugc.com, then run `clipugc auth login`.')
|
|
17
|
+
.version(getVersion(), '-v, --version', 'Print the CLI version')
|
|
18
|
+
.option('--json', 'Output raw JSON (for scripting)')
|
|
19
|
+
.showHelpAfterError('(add --help for usage)')
|
|
20
|
+
.showSuggestionAfterError(true);
|
|
21
|
+
registerAuthCommands(program);
|
|
22
|
+
registerConfigCommands(program);
|
|
23
|
+
registerCreditsCommands(program);
|
|
24
|
+
registerAccountCommands(program);
|
|
25
|
+
registerCharactersCommands(program);
|
|
26
|
+
registerImagesCommands(program);
|
|
27
|
+
registerVideosCommands(program);
|
|
28
|
+
registerHooksCommands(program);
|
|
29
|
+
return program;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,UAAU,SAAS;IACvB,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,OAAO;SACJ,IAAI,CAAC,SAAS,CAAC;SACf,WAAW,CACV,yGAAyG;QACzG,2FAA2F,CAC5F;SACA,OAAO,CAAC,UAAU,EAAE,EAAE,eAAe,EAAE,uBAAuB,CAAC;SAC/D,MAAM,CAAC,QAAQ,EAAE,iCAAiC,CAAC;SACnD,kBAAkB,CAAC,wBAAwB,CAAC;SAC5C,wBAAwB,CAAC,IAAI,CAAC,CAAC;IAElC,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC9B,sBAAsB,CAAC,OAAO,CAAC,CAAC;IAChC,uBAAuB,CAAC,OAAO,CAAC,CAAC;IACjC,uBAAuB,CAAC,OAAO,CAAC,CAAC;IACjC,0BAA0B,CAAC,OAAO,CAAC,CAAC;IACpC,sBAAsB,CAAC,OAAO,CAAC,CAAC;IAChC,sBAAsB,CAAC,OAAO,CAAC,CAAC;IAChC,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAE/B,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
export interface ConfirmDeps {
|
|
3
|
+
confirm: (message: string) => Promise<boolean>;
|
|
4
|
+
promptInput: (message: string) => Promise<string>;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Double confirmation for account deletion: a yes/No confirm followed by
|
|
8
|
+
* typing the exact word DELETE. Throws AbortedError when either step fails.
|
|
9
|
+
* Skipped entirely when `yes` is true (for scripting via --yes).
|
|
10
|
+
*/
|
|
11
|
+
export declare function confirmAccountDeletion(yes: boolean, deps?: ConfirmDeps): Promise<void>;
|
|
12
|
+
export declare function registerAccountCommands(program: Command): void;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createApiClient } from '../services/api.js';
|
|
2
|
+
import { deleteAccount } from '../services/user.service.js';
|
|
3
|
+
import { loadConfig, saveConfig } from '../utils/config.js';
|
|
4
|
+
import { AbortedError } from '../utils/errors.js';
|
|
5
|
+
import { logger } from '../utils/logger.js';
|
|
6
|
+
import { isJsonMode, printJson } from '../utils/output.js';
|
|
7
|
+
import { confirm as confirmPrompt, promptInput } from '../utils/prompt.js';
|
|
8
|
+
/**
|
|
9
|
+
* Double confirmation for account deletion: a yes/No confirm followed by
|
|
10
|
+
* typing the exact word DELETE. Throws AbortedError when either step fails.
|
|
11
|
+
* Skipped entirely when `yes` is true (for scripting via --yes).
|
|
12
|
+
*/
|
|
13
|
+
export async function confirmAccountDeletion(yes, deps = { confirm: confirmPrompt, promptInput }) {
|
|
14
|
+
if (yes)
|
|
15
|
+
return;
|
|
16
|
+
const proceed = await deps.confirm('This permanently deletes your ClipUGC account and all characters/videos. Continue?');
|
|
17
|
+
if (!proceed) {
|
|
18
|
+
throw new AbortedError('Account deletion aborted.');
|
|
19
|
+
}
|
|
20
|
+
const typed = await deps.promptInput('Type DELETE to confirm: ');
|
|
21
|
+
if (typed !== 'DELETE') {
|
|
22
|
+
throw new AbortedError('Account deletion aborted (confirmation text did not match).');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function registerAccountCommands(program) {
|
|
26
|
+
const account = program
|
|
27
|
+
.command('account')
|
|
28
|
+
.description('Manage your ClipUGC account');
|
|
29
|
+
account
|
|
30
|
+
.command('delete')
|
|
31
|
+
.description('Permanently delete your ClipUGC account and all its characters and videos (irreversible)')
|
|
32
|
+
.option('--yes', 'Skip both confirmation prompts (for scripting)')
|
|
33
|
+
.action(async (options, cmd) => {
|
|
34
|
+
await confirmAccountDeletion(Boolean(options.yes));
|
|
35
|
+
const api = await createApiClient();
|
|
36
|
+
await deleteAccount(api);
|
|
37
|
+
const config = await loadConfig();
|
|
38
|
+
config.apiKey = '';
|
|
39
|
+
config.email = '';
|
|
40
|
+
await saveConfig(config);
|
|
41
|
+
if (isJsonMode(cmd)) {
|
|
42
|
+
printJson({ deleted: true });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
logger.success('Your ClipUGC account has been deleted and the stored API key removed.');
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=account.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account.js","sourceRoot":"","sources":["../../src/commands/account.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAO3E;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,GAAY,EACZ,OAAoB,EAAE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE;IAE3D,IAAI,GAAG;QAAE,OAAO;IAEhB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAChC,oFAAoF,CACrF,CAAC;IACF,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,YAAY,CAAC,2BAA2B,CAAC,CAAC;IACtD,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,0BAA0B,CAAC,CAAC;IACjE,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvB,MAAM,IAAI,YAAY,CAAC,6DAA6D,CAAC,CAAC;IACxF,CAAC;AACH,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,OAAgB;IACtD,MAAM,OAAO,GAAG,OAAO;SACpB,OAAO,CAAC,SAAS,CAAC;SAClB,WAAW,CAAC,6BAA6B,CAAC,CAAC;IAE9C,OAAO;SACJ,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,0FAA0F,CAAC;SACvG,MAAM,CAAC,OAAO,EAAE,gDAAgD,CAAC;SACjE,MAAM,CAAC,KAAK,EAAE,OAA0B,EAAE,GAAY,EAAE,EAAE;QACzD,MAAM,sBAAsB,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;QAEnD,MAAM,GAAG,GAAG,MAAM,eAAe,EAAE,CAAC;QACpC,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC;QAEzB,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;QAClC,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC;QACnB,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC;QAClB,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC;QAEzB,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACpB,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YAC7B,OAAO;QACT,CAAC;QACD,MAAM,CAAC,OAAO,CAAC,uEAAuE,CAAC,CAAC;IAC1F,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import { type UserProfile } from '../services/user.service.js';
|
|
3
|
+
/** Mask an API key for display: first 4 chars + ellipsis. */
|
|
4
|
+
export declare function maskApiKey(apiKey: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Validate an API key against GET /user and persist apiKey + email in config.
|
|
7
|
+
* Throws AuthError (with a key-specific message) when the key is rejected.
|
|
8
|
+
*/
|
|
9
|
+
export declare function performLogin(apiKey: string): Promise<UserProfile>;
|
|
10
|
+
/** Remove the stored apiKey + email from the config file. Idempotent. */
|
|
11
|
+
export declare function performLogout(): Promise<void>;
|
|
12
|
+
/** Print a user profile as key/value lines, skipping null/empty fields. */
|
|
13
|
+
export declare function printUserProfile(user: UserProfile): void;
|
|
14
|
+
export declare function registerAuthCommands(program: Command): void;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { createApiClient } from '../services/api.js';
|
|
2
|
+
import { getUser } from '../services/user.service.js';
|
|
3
|
+
import { getConfigPath, loadConfig, resolveApiBaseUrl, resolveApiKey, saveConfig } from '../utils/config.js';
|
|
4
|
+
import { AuthError, ValidationError } from '../utils/errors.js';
|
|
5
|
+
import { logger } from '../utils/logger.js';
|
|
6
|
+
import { isJsonMode, printJson } from '../utils/output.js';
|
|
7
|
+
import { promptSecret } from '../utils/prompt.js';
|
|
8
|
+
const DASHBOARD_URL = 'https://clipugc.com/dashboard';
|
|
9
|
+
/** Mask an API key for display: first 4 chars + ellipsis. */
|
|
10
|
+
export function maskApiKey(apiKey) {
|
|
11
|
+
if (!apiKey)
|
|
12
|
+
return '';
|
|
13
|
+
return `${apiKey.slice(0, 4)}…`;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Validate an API key against GET /user and persist apiKey + email in config.
|
|
17
|
+
* Throws AuthError (with a key-specific message) when the key is rejected.
|
|
18
|
+
*/
|
|
19
|
+
export async function performLogin(apiKey) {
|
|
20
|
+
if (!apiKey) {
|
|
21
|
+
throw new ValidationError(`No API key provided. Create one in your ClipUGC dashboard at ${DASHBOARD_URL}.`);
|
|
22
|
+
}
|
|
23
|
+
const api = await createApiClient({ apiKey });
|
|
24
|
+
let user;
|
|
25
|
+
try {
|
|
26
|
+
user = await getUser(api);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error instanceof AuthError) {
|
|
30
|
+
throw new AuthError(`That API key is invalid or has been revoked. Create a new one in your ClipUGC dashboard at ${DASHBOARD_URL} and try again.`);
|
|
31
|
+
}
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
const config = await loadConfig();
|
|
35
|
+
config.apiKey = apiKey;
|
|
36
|
+
config.email = typeof user.email === 'string' ? user.email : '';
|
|
37
|
+
await saveConfig(config);
|
|
38
|
+
return user;
|
|
39
|
+
}
|
|
40
|
+
/** Remove the stored apiKey + email from the config file. Idempotent. */
|
|
41
|
+
export async function performLogout() {
|
|
42
|
+
const config = await loadConfig();
|
|
43
|
+
config.apiKey = '';
|
|
44
|
+
config.email = '';
|
|
45
|
+
await saveConfig(config);
|
|
46
|
+
}
|
|
47
|
+
/** Print a user profile as key/value lines, skipping null/empty fields. */
|
|
48
|
+
export function printUserProfile(user) {
|
|
49
|
+
for (const [key, value] of Object.entries(user)) {
|
|
50
|
+
if (value === null || value === undefined || value === '')
|
|
51
|
+
continue;
|
|
52
|
+
const rendered = typeof value === 'object' ? JSON.stringify(value) : String(value);
|
|
53
|
+
logger.kv(key, rendered);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export function registerAuthCommands(program) {
|
|
57
|
+
const auth = program
|
|
58
|
+
.command('auth')
|
|
59
|
+
.description('Log in and out of ClipUGC and inspect authentication status');
|
|
60
|
+
auth
|
|
61
|
+
.command('login')
|
|
62
|
+
.description(`Log in with an API key (create one in your ClipUGC dashboard at ${DASHBOARD_URL})`)
|
|
63
|
+
.option('--api-key <key>', 'API key from your ClipUGC dashboard (prompted securely if omitted)')
|
|
64
|
+
.action(async (options, cmd) => {
|
|
65
|
+
const json = isJsonMode(cmd);
|
|
66
|
+
let apiKey = options.apiKey;
|
|
67
|
+
if (!apiKey) {
|
|
68
|
+
if (!json) {
|
|
69
|
+
logger.info(`Create an API key in the ClipUGC dashboard at ${DASHBOARD_URL}`);
|
|
70
|
+
}
|
|
71
|
+
apiKey = await promptSecret('API key: ');
|
|
72
|
+
}
|
|
73
|
+
const user = await performLogin(apiKey);
|
|
74
|
+
const email = typeof user.email === 'string' && user.email ? user.email : null;
|
|
75
|
+
if (json) {
|
|
76
|
+
printJson({ authenticated: true, email });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
logger.success(`Logged in as ${email ?? 'unknown user'}`);
|
|
80
|
+
logger.hint('Try: clipugc characters list');
|
|
81
|
+
});
|
|
82
|
+
auth
|
|
83
|
+
.command('status')
|
|
84
|
+
.description('Show the config file, API base URL, and whether the stored API key is valid')
|
|
85
|
+
.action(async (_options, cmd) => {
|
|
86
|
+
const json = isJsonMode(cmd);
|
|
87
|
+
const config = await loadConfig();
|
|
88
|
+
const apiBaseUrl = await resolveApiBaseUrl();
|
|
89
|
+
const apiKey = await resolveApiKey();
|
|
90
|
+
if (!apiKey) {
|
|
91
|
+
if (json) {
|
|
92
|
+
printJson({ authenticated: false, email: null, apiBaseUrl });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
logger.kv('Config file', getConfigPath());
|
|
96
|
+
logger.kv('API base URL', apiBaseUrl);
|
|
97
|
+
logger.kv('API key', null);
|
|
98
|
+
logger.info('Not logged in. Run `clipugc auth login`.');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
let authenticated = false;
|
|
102
|
+
let email = config.email || null;
|
|
103
|
+
try {
|
|
104
|
+
const api = await createApiClient({ apiKey });
|
|
105
|
+
const user = await getUser(api);
|
|
106
|
+
authenticated = true;
|
|
107
|
+
if (typeof user.email === 'string' && user.email)
|
|
108
|
+
email = user.email;
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (!(error instanceof AuthError))
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
if (json) {
|
|
115
|
+
printJson({ authenticated, email: authenticated ? email : null, apiBaseUrl });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
logger.kv('Config file', getConfigPath());
|
|
119
|
+
logger.kv('API base URL', apiBaseUrl);
|
|
120
|
+
logger.kv('API key', maskApiKey(apiKey));
|
|
121
|
+
if (authenticated) {
|
|
122
|
+
logger.success(`authenticated as ${email ?? 'unknown user'}`);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
logger.warn('key invalid — run `clipugc auth login` with a fresh key');
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
auth
|
|
129
|
+
.command('logout')
|
|
130
|
+
.description('Remove the stored API key from the config file')
|
|
131
|
+
.action(async (_options, cmd) => {
|
|
132
|
+
await performLogout();
|
|
133
|
+
if (isJsonMode(cmd)) {
|
|
134
|
+
printJson({ loggedOut: true });
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
logger.success(`Logged out. API key removed from ${getConfigPath()}`);
|
|
138
|
+
});
|
|
139
|
+
program
|
|
140
|
+
.command('whoami')
|
|
141
|
+
.description('Show the profile of the authenticated user')
|
|
142
|
+
.action(async (_options, cmd) => {
|
|
143
|
+
const api = await createApiClient();
|
|
144
|
+
const user = await getUser(api);
|
|
145
|
+
if (isJsonMode(cmd)) {
|
|
146
|
+
printJson(user);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
printUserProfile(user);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}
|