wmstudio-cli 0.2.3 → 0.2.5
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/CHANGELOG.md +12 -0
- package/README.md +103 -187
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/package.json +4 -5
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -1,231 +1,147 @@
|
|
|
1
|
-
#
|
|
1
|
+
# wmstudio-cli
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
> Generate images, videos, brand campaigns, and 3D assets from your terminal.
|
|
3
|
+
Command-line client for [WM Studio](https://wmstudio.io). Generate images, videos, brand campaigns, and 3D assets — and inspect your jobs and credits — straight from your terminal.
|
|
5
4
|
|
|
6
|
-
```
|
|
7
|
-
npm i -g
|
|
5
|
+
```bash
|
|
6
|
+
npm i -g wmstudio-cli
|
|
8
7
|
wm login
|
|
9
|
-
wm gen image "
|
|
10
|
-
wm gen video "slow dolly-in on the mug, golden hour" -i ./mug.png -o ad.mp4
|
|
11
|
-
wm campaign "premium oat milk launch, Scandinavian minimal" --variations 3
|
|
8
|
+
wm gen image "studio portrait of a husky, cinematic lighting"
|
|
12
9
|
```
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
---
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
`~/wm/wm-cli/` and will be moved to its own GitHub repo (`PrincipeRosso/wm-cli`)
|
|
18
|
-
before the first npm release. No `pnpm install` has been run yet — that's the
|
|
19
|
-
first action a maintainer takes when they pick this up.
|
|
13
|
+
## Install
|
|
20
14
|
|
|
21
|
-
|
|
15
|
+
Requires Node.js **20.10+**.
|
|
22
16
|
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
```bash
|
|
18
|
+
# global (recommended)
|
|
19
|
+
npm i -g wmstudio-cli
|
|
25
20
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
| `wmstudio` | TS/Next | Web app + REST API |
|
|
29
|
-
| `director-cut` | Python | Desktop backend + embedded MCP |
|
|
30
|
-
| `mcp-director` | Python | Hosted Streamable HTTP MCP |
|
|
31
|
-
| **`wm-cli`** | **TS** | **`wm` CLI on npm** |
|
|
21
|
+
# or per-project
|
|
22
|
+
npm i -D wmstudio-cli
|
|
32
23
|
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
# one-off, no install
|
|
25
|
+
npx wmstudio-cli --help
|
|
26
|
+
```
|
|
35
27
|
|
|
36
|
-
|
|
28
|
+
Verify:
|
|
37
29
|
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
│ wm (CLI) │ ───────────────────────▶ │ wmstudio REST API │
|
|
41
|
-
└──────────────┘ over HTTPS / undici └──────────────────┘
|
|
42
|
-
▲
|
|
43
|
-
│ same endpoints
|
|
44
|
-
│
|
|
45
|
-
┌──────────────────┐
|
|
46
|
-
│ mcp-director │
|
|
47
|
-
└──────────────────┘
|
|
30
|
+
```bash
|
|
31
|
+
wm --version
|
|
48
32
|
```
|
|
49
33
|
|
|
50
|
-
|
|
51
|
-
protocol — there's no reason for a short-lived terminal process to negotiate
|
|
52
|
-
Streamable HTTP + OAuth PKCE when an API key works fine.
|
|
53
|
-
|
|
54
|
-
## Tech stack
|
|
55
|
-
|
|
56
|
-
| Concern | Pick | Why |
|
|
57
|
-
| -------------- | --------------------------------- | -------------------------------------------------- |
|
|
58
|
-
| Language | TypeScript 5.6, strict, ESM | Reuse `wmstudio` types later via a published pkg |
|
|
59
|
-
| CLI framework | `commander` | Small, fast cold start, mature |
|
|
60
|
-
| Prompts | `@inquirer/prompts` | Modular, tree-shakeable, async/await native |
|
|
61
|
-
| HTTP | `undici` | Built-in to Node 20+, fastest, AbortController |
|
|
62
|
-
| Schema | `zod` | Same lib `wmstudio` already uses |
|
|
63
|
-
| Bundler | `tsup` | Single ESM bundle, fast, zero config drift |
|
|
64
|
-
| Lint | ESLint v9 flat + `typescript-eslint` | Type-aware rules |
|
|
65
|
-
| Format | Prettier | Matches `wmstudio` house style |
|
|
66
|
-
| Tests | Vitest + `msw` | Same runner as `wmstudio`; MSW for HTTP fixtures |
|
|
67
|
-
| Release | Changesets + GitHub Actions | One source of truth for versions + npm publish |
|
|
68
|
-
| Node target | 20 LTS | `.nvmrc` pinned |
|
|
34
|
+
## Authenticate
|
|
69
35
|
|
|
70
|
-
|
|
36
|
+
**Step 1 — get an API key.** Sign in at <https://wmstudio.io>, then go to:
|
|
71
37
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
| `wm whoami` | Show account + remaining credits |
|
|
76
|
-
| `wm gen image <prompt>`| Text-to-image / image-to-image. `-o` to download. |
|
|
77
|
-
| `wm gen video <prompt>`| Text-to-video / image-to-video. Polls until done. |
|
|
78
|
-
| `wm upscale <url>` | Topaz upscale 1–4× |
|
|
79
|
-
| `wm campaign <brief>` | Full director_* pipeline. `--variations N` for parallel runs. |
|
|
80
|
-
| `wm jobs list` | Recent generation jobs |
|
|
81
|
-
| `wm jobs get <id>` | Single job status |
|
|
82
|
-
|
|
83
|
-
Global flags: `--api-url`, `--api-key`, `--json`, `-v / --version`, `--help`.
|
|
84
|
-
|
|
85
|
-
## Auth precedence
|
|
86
|
-
|
|
87
|
-
1. `--api-key` flag
|
|
88
|
-
2. `WM_API_KEY` env var
|
|
89
|
-
3. `~/.wm/config.json` (written by `wm login`)
|
|
90
|
-
4. _(none → exits with `auth_required`, code 10)_
|
|
91
|
-
|
|
92
|
-
## Exit codes
|
|
93
|
-
|
|
94
|
-
Stable contract, mirrored across `wm-cli` and `mcp-director`. See
|
|
95
|
-
[`docs/CONVENTIONS.md`](./docs/CONVENTIONS.md) §3.
|
|
96
|
-
|
|
97
|
-
| Code | Meaning |
|
|
98
|
-
| ---- | ------------------- |
|
|
99
|
-
| 0 | OK |
|
|
100
|
-
| 2 | Usage error |
|
|
101
|
-
| 10 | Not logged in |
|
|
102
|
-
| 11 | Invalid API key |
|
|
103
|
-
| 20 | Asset URL required |
|
|
104
|
-
| 30 | Upgrade required |
|
|
105
|
-
| 31 | Rate limited |
|
|
106
|
-
| 40 | Network error |
|
|
107
|
-
| 50 | Server error |
|
|
108
|
-
| 51 | Timeout |
|
|
109
|
-
| 99 | Unexpected crash |
|
|
110
|
-
|
|
111
|
-
## First-time setup (when you pick this up)
|
|
38
|
+
> **Dashboard → API keys → Create new key**
|
|
39
|
+
>
|
|
40
|
+
> Direct link: <https://wmstudio.io/dashboard/api-keys>
|
|
112
41
|
|
|
113
|
-
|
|
114
|
-
cd ~/wm/wm-cli # or wherever you moved it
|
|
115
|
-
nvm use # honours .nvmrc → Node 20
|
|
116
|
-
corepack enable # ensures pnpm 9 is available
|
|
117
|
-
pnpm install # this is what resolves all the "Cannot find module" lints
|
|
118
|
-
pnpm typecheck
|
|
119
|
-
pnpm test
|
|
120
|
-
pnpm build
|
|
121
|
-
node dist/index.js --help
|
|
122
|
-
```
|
|
42
|
+
Copy the key once — it's shown only at creation. Keys start with `wm_live_…`.
|
|
123
43
|
|
|
124
|
-
|
|
44
|
+
**Step 2 — log in from your terminal.**
|
|
125
45
|
|
|
126
46
|
```bash
|
|
127
|
-
|
|
47
|
+
wm login # paste the key when prompted (input is masked)
|
|
48
|
+
wm whoami # prints account email + credit balance
|
|
128
49
|
```
|
|
129
50
|
|
|
130
|
-
|
|
51
|
+
Credentials are stored in `~/.wm/config.json` (chmod `600`). To sign out:
|
|
131
52
|
|
|
132
|
-
```
|
|
133
|
-
wm
|
|
134
|
-
├── src/
|
|
135
|
-
│ ├── index.ts Entrypoint (shebang banner via tsup)
|
|
136
|
-
│ ├── cli.ts commander root, registers subcommands
|
|
137
|
-
│ ├── client.ts WmApiClient — thin undici-based HTTP layer
|
|
138
|
-
│ ├── config.ts ~/.wm/config.json + env merge
|
|
139
|
-
│ ├── constants.ts Defaults, env var names, model ids
|
|
140
|
-
│ ├── errors.ts WmCliError + exit code map
|
|
141
|
-
│ ├── logger.ts Pretty + JSON structured logger
|
|
142
|
-
│ ├── util/
|
|
143
|
-
│ │ ├── download.ts Stream URL → file
|
|
144
|
-
│ │ └── poll.ts Generic async-job poller
|
|
145
|
-
│ └── commands/
|
|
146
|
-
│ ├── _shared.ts Ctx, requireAuth, renderResult
|
|
147
|
-
│ ├── login.ts
|
|
148
|
-
│ ├── whoami.ts
|
|
149
|
-
│ ├── gen.ts image + video subcommands
|
|
150
|
-
│ ├── upscale.ts
|
|
151
|
-
│ ├── campaign.ts director_* pipeline
|
|
152
|
-
│ └── jobs.ts
|
|
153
|
-
├── test/ Vitest + MSW
|
|
154
|
-
├── docs/
|
|
155
|
-
│ └── CONVENTIONS.md ← cross-repo contract (sync to other repos)
|
|
156
|
-
├── .changeset/ Changesets config + per-PR notes
|
|
157
|
-
├── .github/workflows/ CI + release pipelines
|
|
158
|
-
├── tsup.config.ts
|
|
159
|
-
├── vitest.config.ts
|
|
160
|
-
├── eslint.config.js
|
|
161
|
-
├── tsconfig.json
|
|
162
|
-
├── package.json
|
|
163
|
-
├── .env.example
|
|
164
|
-
├── .editorconfig ← shared across all wm-* repos
|
|
165
|
-
├── .nvmrc 20.18.0
|
|
166
|
-
├── LICENSE MIT
|
|
167
|
-
└── CHANGELOG.md
|
|
53
|
+
```bash
|
|
54
|
+
wm logout
|
|
168
55
|
```
|
|
169
56
|
|
|
170
|
-
|
|
57
|
+
You can also pass a key per-call without saving it:
|
|
171
58
|
|
|
172
59
|
```bash
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
git add .
|
|
177
|
-
git commit -m "chore: import initial scaffold"
|
|
178
|
-
git remote add origin git@github.com:PrincipeRosso/wm-cli.git
|
|
179
|
-
git push -u origin main
|
|
60
|
+
wm --api-key wm_live_xxx whoami
|
|
61
|
+
# or
|
|
62
|
+
WM_API_KEY=wm_live_xxx wm whoami
|
|
180
63
|
```
|
|
181
64
|
|
|
182
|
-
|
|
183
|
-
|
|
65
|
+
## Commands
|
|
66
|
+
|
|
67
|
+
| Command | What it does |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `wm login` | Save an API key for this machine |
|
|
70
|
+
| `wm logout` | Remove saved credentials |
|
|
71
|
+
| `wm whoami` | Show account email + credits remaining |
|
|
72
|
+
| `wm gen image <prompt>` | Generate an image |
|
|
73
|
+
| `wm gen video <prompt>` | Generate a video |
|
|
74
|
+
| `wm upscale <image>` | Upscale an image (URL or local file) |
|
|
75
|
+
| `wm campaign <brief>` | Run a multi-asset brand campaign |
|
|
76
|
+
| `wm jobs list` | List your recent generation jobs |
|
|
77
|
+
| `wm jobs get <id>` | Show details for a single job |
|
|
184
78
|
|
|
79
|
+
Run `wm <command> --help` for full flags.
|
|
80
|
+
|
|
81
|
+
## Examples
|
|
185
82
|
|
|
186
|
-
## Commands
|
|
187
83
|
```bash
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
84
|
+
# Image — pick a model and aspect ratio, save the result locally
|
|
85
|
+
wm gen image "neon cyberpunk alley, rain, dramatic lighting" \
|
|
86
|
+
--model flux-pro --aspect 16:9 --out ./out/
|
|
87
|
+
|
|
88
|
+
# Video — 5 second clip from a text prompt
|
|
89
|
+
wm gen video "drone shot flying over snowy mountain peaks at sunrise" \
|
|
90
|
+
--duration 5 --out ./videos/
|
|
91
|
+
|
|
92
|
+
# Upscale a local file 4x
|
|
93
|
+
wm upscale ./photo.jpg --scale 4 --out ./upscaled/
|
|
94
|
+
|
|
95
|
+
# Brand campaign from a single brief
|
|
96
|
+
wm campaign "Launch teaser for an artisanal coffee brand called Brava" \
|
|
97
|
+
--out ./campaigns/brava/
|
|
98
|
+
|
|
99
|
+
# Inspect jobs
|
|
100
|
+
wm jobs list --limit 10
|
|
101
|
+
wm jobs get gen_01HZX...
|
|
102
|
+
|
|
103
|
+
# Machine-readable output for scripting
|
|
104
|
+
wm --json jobs list --limit 5 | jq '.[] | .id'
|
|
197
105
|
```
|
|
198
106
|
|
|
107
|
+
## Global flags
|
|
108
|
+
|
|
109
|
+
| Flag | Env var | Notes |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| `--api-key <key>` | `WM_API_KEY` | Overrides saved credentials |
|
|
112
|
+
| `--api-url <url>` | `WMSTUDIO_API_URL` | Point at a different deployment (default `https://wmstudio.io/api`) |
|
|
113
|
+
| `--json` | — | Emit JSON instead of formatted text |
|
|
114
|
+
| `-v, --version` | — | Print CLI version |
|
|
115
|
+
|
|
116
|
+
## Where things live
|
|
199
117
|
|
|
118
|
+
- **Config:** `~/.wm/config.json` — API key + base URL. Per-OS-user, never shared.
|
|
119
|
+
- **Outputs:** wherever you point `--out`, default is the current directory.
|
|
120
|
+
- **Logs:** stderr. Use `--json` for parseable stdout in scripts.
|
|
121
|
+
|
|
122
|
+
To test as a fresh user without touching your real session:
|
|
200
123
|
|
|
201
|
-
## Commands to run for pushing. anew version
|
|
202
124
|
```bash
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
pnpm changeset version # bumps version + CHANGELOG
|
|
207
|
-
git commit -am "chore: release"
|
|
208
|
-
pnpm publish --access public
|
|
125
|
+
HOME=/tmp/wm-fresh wm login
|
|
126
|
+
HOME=/tmp/wm-fresh wm whoami
|
|
127
|
+
rm -rf /tmp/wm-fresh
|
|
209
128
|
```
|
|
210
129
|
|
|
130
|
+
## Troubleshooting
|
|
211
131
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
npm
|
|
132
|
+
- **`401 Unauthorized`** — key is missing, revoked, or expired. Run `wm login` again.
|
|
133
|
+
- **`402 Insufficient credits`** — top up at <https://wmstudio.io/dashboard/billing>.
|
|
134
|
+
- **Job stuck in `queued`** — the CLI polls automatically; if it times out, fetch later with `wm jobs get <id>`.
|
|
135
|
+
- **`command not found: wm`** — your global `node_modules/.bin` isn't in `$PATH`. Run `npm prefix -g` and add `<prefix>/bin` to `$PATH`.
|
|
216
136
|
|
|
217
|
-
|
|
218
|
-
wm --version
|
|
137
|
+
## Links
|
|
219
138
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
139
|
+
- Website: <https://wmstudio.io>
|
|
140
|
+
- Docs: <https://wmstudio.io/docs/cli>
|
|
141
|
+
- Dashboard: <https://wmstudio.io/dashboard>
|
|
142
|
+
- API keys: <https://wmstudio.io/dashboard/api-keys>
|
|
143
|
+
- Support: <https://wmstudio.io/support>
|
|
223
144
|
|
|
224
|
-
|
|
225
|
-
wm login # paste the sk_live_... token
|
|
145
|
+
## License
|
|
226
146
|
|
|
227
|
-
|
|
228
|
-
wm whoami
|
|
229
|
-
wm gen image "a single red apple" -o apple.png
|
|
230
|
-
file apple.png # → PNG image data
|
|
231
|
-
```
|
|
147
|
+
MIT © WM Studio
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { Command } from "commander";
|
|
|
5
5
|
|
|
6
6
|
// src/commands/login.ts
|
|
7
7
|
import { password } from "@inquirer/prompts";
|
|
8
|
+
import kleur2 from "kleur";
|
|
8
9
|
|
|
9
10
|
// src/config.ts
|
|
10
11
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from "fs";
|
|
@@ -16,10 +17,12 @@ import { z } from "zod";
|
|
|
16
17
|
var DEFAULTS = {
|
|
17
18
|
/** Hosted WM Studio REST API base. Override via `WMSTUDIO_API_URL`. */
|
|
18
19
|
apiUrl: "https://wmstudio.io/api",
|
|
19
|
-
/**
|
|
20
|
-
uploadUrl: "https://
|
|
20
|
+
/** Page surfaced when the API requires a public asset URL. */
|
|
21
|
+
uploadUrl: "https://wmstudio.io/dashboard/uploads",
|
|
21
22
|
/** Credit top-up landing page. */
|
|
22
23
|
upgradeUrl: "https://wmstudio.io/dashboard/credits",
|
|
24
|
+
/** Where users create API keys. Shown by `wm login`. */
|
|
25
|
+
apiKeysUrl: "https://wmstudio.io/dashboard/api-keys",
|
|
23
26
|
/** Below this remaining-credit count we surface a warning. */
|
|
24
27
|
lowCreditsThreshold: 50,
|
|
25
28
|
/** Default request timeout (ms) for non-job calls. */
|
|
@@ -336,6 +339,13 @@ var logger = {
|
|
|
336
339
|
// src/commands/login.ts
|
|
337
340
|
function registerLogin(program) {
|
|
338
341
|
program.command("login").description("Save your WM Studio API key to ~/.wm/config.json (chmod 0600).").option("--key <apiKey>", "Pass the API key non-interactively (useful in CI).").action(async (opts) => {
|
|
342
|
+
if (!opts.key) {
|
|
343
|
+
process.stderr.write(
|
|
344
|
+
`
|
|
345
|
+
${kleur2.bold("Get an API key:")} ${kleur2.cyan(DEFAULTS.apiKeysUrl)}
|
|
346
|
+
` + kleur2.dim(" Sign in \u2192 Dashboard \u2192 API keys \u2192 Create new key, then paste below.\n\n")
|
|
347
|
+
);
|
|
348
|
+
}
|
|
339
349
|
const apiKey = opts.key ?? await password({
|
|
340
350
|
message: "Paste your WM Studio API key:",
|
|
341
351
|
mask: "*"
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/commands/login.ts","../src/config.ts","../src/constants.ts","../src/client.ts","../src/errors.ts","../src/logger.ts","../src/commands/_shared.ts","../src/commands/whoami.ts","../src/commands/gen.ts","../src/util/download.ts","../src/util/poll.ts","../src/util/await-job.ts","../src/commands/upscale.ts","../src/commands/campaign.ts","../src/commands/jobs.ts","../src/index.ts"],"sourcesContent":["/**\n * Commander root. Each subcommand lives in its own file under ./commands.\n */\nimport { Command } from \"commander\"\nimport { registerLogin } from \"./commands/login.js\"\nimport { registerWhoami } from \"./commands/whoami.js\"\nimport { registerGen } from \"./commands/gen.js\"\nimport { registerUpscale } from \"./commands/upscale.js\"\nimport { registerCampaign } from \"./commands/campaign.js\"\nimport { registerJobs } from \"./commands/jobs.js\"\n\nexport async function run(argv: string[]): Promise<void> {\n const program = new Command()\n .name(\"wm\")\n .description(\n \"WM Studio command-line client. Generate images, videos, brand campaigns, and 3D assets.\"\n )\n .version(VERSION, \"-v, --version\")\n .option(\"--api-url <url>\", \"Override WM Studio API base URL\")\n .option(\"--api-key <key>\", \"Override API key (otherwise WM_API_KEY or ~/.wm/config.json)\")\n .option(\"--json\", \"Emit machine-readable JSON output\", false)\n .showHelpAfterError(\"(run `wm <command> --help` for details)\")\n\n registerLogin(program)\n registerWhoami(program)\n registerGen(program)\n registerUpscale(program)\n registerCampaign(program)\n registerJobs(program)\n\n await program.parseAsync(argv)\n}\n\n// Injected at build time by tsup via `define` — fallback for `tsx`/`vitest`.\ndeclare const __VERSION__: string | undefined\nconst VERSION: string =\n (typeof __VERSION__ !== \"undefined\" && __VERSION__) ||\n process.env.npm_package_version ||\n \"0.0.0-dev\"\n","import type { Command } from \"commander\"\nimport { password } from \"@inquirer/prompts\"\nimport { writeFileConfig } from \"../config.js\"\nimport { WmApiClient } from \"../client.js\"\nimport { resolveConfig } from \"../config.js\"\nimport { logger } from \"../logger.js\"\nimport { WmCliError } from \"../errors.js\"\n\nexport function registerLogin(program: Command): void {\n program\n .command(\"login\")\n .description(\"Save your WM Studio API key to ~/.wm/config.json (chmod 0600).\")\n .option(\"--key <apiKey>\", \"Pass the API key non-interactively (useful in CI).\")\n .action(async (opts: { key?: string }) => {\n const apiKey =\n opts.key ??\n (await password({\n message: \"Paste your WM Studio API key:\",\n mask: \"*\",\n }))\n if (!apiKey || apiKey.trim().length === 0) {\n throw WmCliError.usage(\"API key cannot be empty.\")\n }\n\n const cfg = resolveConfig({ apiKey: apiKey.trim() })\n const client = new WmApiClient(cfg)\n const me = await client.whoami()\n\n writeFileConfig({ apiKey: apiKey.trim() })\n logger.info(`Logged in as ${me.email} (${me.creditsRemaining} credits remaining).`)\n })\n}\n","/**\n * On-disk config + env merge.\n *\n * Precedence (highest first):\n * 1. CLI flag (--api-url, --api-key)\n * 2. Environment variables (WMSTUDIO_API_URL, WM_API_KEY)\n * 3. ~/.wm/config.json\n * 4. Built-in defaults (constants.ts)\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport { dirname, join } from \"node:path\"\nimport { z } from \"zod\"\nimport { DEFAULTS, ENV } from \"./constants.js\"\n\nconst FileConfigSchema = z.object({\n apiUrl: z.string().url().optional(),\n apiKey: z.string().min(1).optional(),\n uploadUrl: z.string().url().optional(),\n upgradeUrl: z.string().url().optional(),\n lowCreditsThreshold: z.number().int().nonnegative().optional(),\n})\n\nexport type FileConfig = z.infer<typeof FileConfigSchema>\n\nexport interface ResolvedConfig {\n apiUrl: string\n apiKey: string | undefined\n uploadUrl: string\n upgradeUrl: string\n lowCreditsThreshold: number\n configPath: string\n}\n\nexport function configDir(): string {\n const override = process.env[ENV.ConfigDir]\n return override && override.length > 0 ? override : join(homedir(), \".wm\")\n}\n\nexport function configPath(): string {\n return join(configDir(), \"config.json\")\n}\n\nexport function readFileConfig(): FileConfig {\n const path = configPath()\n if (!existsSync(path)) return {}\n try {\n const raw = readFileSync(path, \"utf8\")\n return FileConfigSchema.parse(JSON.parse(raw))\n } catch {\n // Corrupt config — return empty rather than crash the CLI on every command.\n return {}\n }\n}\n\nexport function writeFileConfig(patch: FileConfig): void {\n const merged = { ...readFileConfig(), ...patch }\n const dir = configDir()\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 0o700 })\n const file = configPath()\n writeFileSync(file, JSON.stringify(merged, null, 2) + \"\\n\", { mode: 0o600 })\n // Defensive: enforce 0600 even if file pre-existed.\n try {\n chmodSync(file, 0o600)\n } catch {\n /* non-fatal */\n }\n // And 0700 on the dir.\n try {\n chmodSync(dirname(file), 0o700)\n } catch {\n /* non-fatal */\n }\n}\n\nexport interface ConfigOverrides {\n apiUrl?: string\n apiKey?: string\n}\n\nexport function resolveConfig(overrides: ConfigOverrides = {}): ResolvedConfig {\n const file = readFileConfig()\n return {\n apiUrl: overrides.apiUrl ?? process.env[ENV.ApiUrl] ?? file.apiUrl ?? DEFAULTS.apiUrl,\n apiKey: overrides.apiKey ?? process.env[ENV.ApiKey] ?? file.apiKey,\n uploadUrl: process.env[ENV.UploadUrl] ?? file.uploadUrl ?? DEFAULTS.uploadUrl,\n upgradeUrl: process.env[ENV.UpgradeUrl] ?? file.upgradeUrl ?? DEFAULTS.upgradeUrl,\n lowCreditsThreshold:\n numFromEnv(process.env[ENV.LowCreditsThreshold]) ??\n file.lowCreditsThreshold ??\n DEFAULTS.lowCreditsThreshold,\n configPath: configPath(),\n }\n}\n\nfunction numFromEnv(raw: string | undefined): number | undefined {\n if (!raw) return undefined\n const n = Number(raw)\n return Number.isFinite(n) ? n : undefined\n}\n","/**\n * Shared constants. Keep in sync with `mcp-director/src/config.py`.\n * Anything that names an env var, an endpoint, or a model id belongs here.\n */\n\nexport const DEFAULTS = {\n /** Hosted WM Studio REST API base. Override via `WMSTUDIO_API_URL`. */\n apiUrl: \"https://wmstudio.io/api\",\n /** Public upload endpoint surfaced when an asset URL is missing. */\n uploadUrl: \"https://director-cut.fly.dev/upload\",\n /** Credit top-up landing page. */\n upgradeUrl: \"https://wmstudio.io/dashboard/credits\",\n /** Below this remaining-credit count we surface a warning. */\n lowCreditsThreshold: 50,\n /** Default request timeout (ms) for non-job calls. */\n requestTimeoutMs: 60_000,\n /** Polling interval (ms) for async jobs. */\n jobPollIntervalMs: 4_000,\n /** Hard ceiling for job polling (ms). */\n jobPollTimeoutMs: 15 * 60 * 1000,\n} as const\n\nexport const ENV = {\n ApiUrl: \"WMSTUDIO_API_URL\",\n ApiKey: \"WM_API_KEY\",\n UploadUrl: \"ASSET_UPLOAD_URL\",\n UpgradeUrl: \"CREDITS_UPGRADE_URL\",\n LowCreditsThreshold: \"CREDITS_LOW_THRESHOLD\",\n LogLevel: \"WM_LOG_LEVEL\",\n ConfigDir: \"WM_CONFIG_DIR\",\n} as const\n\n/** Default model ids per tool — mirror `mcp-director/src/tools/studio.py` defaults. */\nexport const DEFAULT_MODELS = {\n image: \"fal-ai/flux/dev\",\n videoText: \"fal-ai/kling-video/v2.5-turbo/pro/text-to-video\",\n videoImage: \"fal-ai/kling-video/v2.5-turbo/pro/image-to-video\",\n upscaleImage: \"fal-ai/topaz/upscale/image\",\n upscaleVideo: \"fal-ai/topaz/upscale/video\",\n threeD: \"fal-ai/meshy/v6/image-to-3d\",\n brandshot: \"fal-ai/flux/dev\",\n cameraAngles: \"fal-ai/flux/dev\",\n casting: \"fal-ai/flux/dev\",\n ugcRoom: \"fal-ai/flux/dev\",\n} as const\n","/**\n * Thin HTTP client for the WM Studio REST API.\n *\n * Endpoints mirror what `mcp-director/src/wmstudio_client.py` already calls\n * — keeping the contract identical means the CLI is a drop-in alternative\n * to the MCP for the same underlying backend.\n */\nimport { z } from \"zod\"\nimport { DEFAULTS } from \"./constants.js\"\nimport { ExitCode, WmCliError } from \"./errors.js\"\nimport type { ResolvedConfig } from \"./config.js\"\n\nconst ApiErrorSchema = z.object({\n code: z.string().optional(),\n error: z.string().optional(),\n message: z.string().optional(),\n requiresTopUp: z.boolean().optional(),\n upgradeUrl: z.string().url().optional(),\n uploadUrl: z.string().url().optional(),\n})\n\nexport interface JsonRequest {\n method: \"GET\" | \"POST\" | \"PATCH\" | \"DELETE\"\n path: string\n body?: Record<string, unknown>\n query?: Record<string, string | number | boolean | undefined>\n timeoutMs?: number\n}\n\nexport class WmApiClient {\n constructor(private readonly cfg: ResolvedConfig) {}\n\n /** GET /me — returns account + credit balance. */\n whoami(): Promise<{\n userId: string\n email: string\n creditsRemaining: number\n plan: string\n }> {\n return this.json({ method: \"GET\", path: \"/me\" })\n }\n\n /** Validate the current api key without side-effects. */\n async ping(): Promise<boolean> {\n try {\n await this.whoami()\n return true\n } catch (err) {\n if (err instanceof WmCliError && err.code === \"auth_invalid\") return false\n throw err\n }\n }\n\n /** Generic JSON helper. */\n async json<T = unknown>(req: JsonRequest): Promise<T> {\n if (!this.cfg.apiKey) throw WmCliError.authRequired()\n\n const url = new URL(req.path.replace(/^\\//, \"\"), this.cfg.apiUrl.replace(/\\/?$/, \"/\"))\n if (req.query) {\n for (const [k, v] of Object.entries(req.query)) {\n if (v !== undefined) url.searchParams.set(k, String(v))\n }\n }\n\n const ac = new AbortController()\n const timeoutMs = req.timeoutMs ?? DEFAULTS.requestTimeoutMs\n const timeout = setTimeout(() => ac.abort(), timeoutMs)\n\n let res: Response\n try {\n res = await fetch(url, {\n method: req.method,\n headers: {\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n authorization: `Bearer ${this.cfg.apiKey}`,\n \"user-agent\": userAgent(),\n },\n body: req.body ? JSON.stringify(req.body) : undefined,\n signal: ac.signal,\n })\n } catch (err) {\n clearTimeout(timeout)\n if (err instanceof WmCliError) throw err\n const aborted = (err as { name?: string } | null)?.name === \"AbortError\"\n throw new WmCliError({\n code: aborted ? \"timeout\" : \"network\",\n exitCode: aborted ? ExitCode.TIMEOUT : ExitCode.NETWORK,\n message: aborted\n ? `Request timed out after ${timeoutMs}ms (${req.method} ${req.path})`\n : `Network error: ${(err as Error).message}`,\n details: { cause: (err as Error).message },\n })\n }\n clearTimeout(timeout)\n\n const text = await res.text()\n const parsed: unknown = text ? safeJson(text) : undefined\n\n if (res.status >= 200 && res.status < 300) {\n return parsed as T\n }\n throw classify(res.status, parsed, this.cfg)\n }\n}\n\nfunction safeJson(text: string): unknown {\n try {\n return JSON.parse(text)\n } catch {\n return { raw: text }\n }\n}\n\nfunction classify(status: number, payload: unknown, cfg: ResolvedConfig): WmCliError {\n const parsed = ApiErrorSchema.safeParse(payload)\n const data = parsed.success ? parsed.data : {}\n const msg = data.message ?? data.error ?? `HTTP ${status}`\n\n if (status === 401 || status === 403) return WmCliError.authInvalid(msg)\n if (status === 402 || data.requiresTopUp) {\n return WmCliError.upgradeRequired(data.upgradeUrl ?? cfg.upgradeUrl, msg)\n }\n if (status === 422 && data.code === \"asset_url_required\") {\n return WmCliError.assetUrlRequired(data.uploadUrl ?? cfg.uploadUrl, msg)\n }\n return new WmCliError({\n code: status >= 500 ? \"server\" : \"usage\",\n exitCode: status >= 500 ? 50 : 2,\n message: msg,\n details: { status, payload },\n })\n}\n\nfunction userAgent(): string {\n return `wm-cli/${process.env.npm_package_version ?? \"dev\"} (node ${process.version})`\n}\n","/**\n * Error model + exit codes for `wm`.\n *\n * Exit codes are stable contract — kept in sync with `mcp-director` error codes\n * (see ../docs/CONVENTIONS.md). Used by CI, shell scripts, and humans.\n */\n\nexport const ExitCode = {\n OK: 0,\n USAGE: 2, // bad arguments / unknown command\n AUTH_REQUIRED: 10, // not logged in / missing api key\n AUTH_INVALID: 11, // server rejected token (401/403)\n ASSET_URL_REQUIRED: 20, // missing or unreachable user-provided URL\n UPGRADE_REQUIRED: 30, // 402 insufficient credits\n RATE_LIMITED: 31, // 429\n NETWORK: 40, // transport / DNS / TLS\n SERVER: 50, // 5xx\n TIMEOUT: 51, // server-side or client-side deadline exceeded\n UNEXPECTED: 99, // crashed / not classifiable\n} as const\n\nexport type ExitCodeValue = (typeof ExitCode)[keyof typeof ExitCode]\n\n/**\n * The canonical error codes returned by the WM Studio API and `mcp-director`.\n * Keeping the same vocabulary across CLI / MCP / SDK is the whole point.\n */\nexport const ErrorCode = {\n AuthRequired: \"auth_required\",\n AuthInvalid: \"auth_invalid\",\n AssetUrlRequired: \"asset_url_required\",\n UpgradeRequired: \"upgrade_required\",\n RateLimited: \"rate_limited\",\n Network: \"network\",\n Server: \"server\",\n Timeout: \"timeout\",\n Usage: \"usage\",\n Unexpected: \"unexpected\",\n} as const\n\nexport type ErrorCodeValue = (typeof ErrorCode)[keyof typeof ErrorCode]\n\nexport class WmCliError extends Error {\n readonly code: ErrorCodeValue\n readonly exitCode: ExitCodeValue\n readonly details?: Record<string, unknown>\n\n constructor(opts: {\n code: ErrorCodeValue\n exitCode: ExitCodeValue\n message: string\n details?: Record<string, unknown>\n }) {\n super(opts.message)\n this.name = \"WmCliError\"\n this.code = opts.code\n this.exitCode = opts.exitCode\n this.details = opts.details\n }\n\n static authRequired(message = \"Not logged in. Run `wm login` first.\"): WmCliError {\n return new WmCliError({\n code: ErrorCode.AuthRequired,\n exitCode: ExitCode.AUTH_REQUIRED,\n message,\n })\n }\n\n static authInvalid(message = \"Invalid or expired API key.\"): WmCliError {\n return new WmCliError({\n code: ErrorCode.AuthInvalid,\n exitCode: ExitCode.AUTH_INVALID,\n message,\n })\n }\n\n static assetUrlRequired(uploadUrl: string, message?: string): WmCliError {\n return new WmCliError({\n code: ErrorCode.AssetUrlRequired,\n exitCode: ExitCode.ASSET_URL_REQUIRED,\n message: message ?? `A real public asset URL is required. Upload at ${uploadUrl}.`,\n details: { uploadUrl },\n })\n }\n\n static upgradeRequired(upgradeUrl: string, message?: string): WmCliError {\n return new WmCliError({\n code: ErrorCode.UpgradeRequired,\n exitCode: ExitCode.UPGRADE_REQUIRED,\n message: message ?? `Insufficient credits. Top up at ${upgradeUrl} and re-run.`,\n details: { upgradeUrl },\n })\n }\n\n static usage(message: string): WmCliError {\n return new WmCliError({\n code: ErrorCode.Usage,\n exitCode: ExitCode.USAGE,\n message,\n })\n }\n}\n","/**\n * Tiny structured logger. Console output is colourised for humans (stderr);\n * when `WM_LOG_FORMAT=json` is set, emits JSON lines compatible with the same\n * shape used by `mcp-director` (`event`, `level`, `ts`, …extra).\n */\nimport kleur from \"kleur\"\nimport { ENV } from \"./constants.js\"\n\ntype Level = \"debug\" | \"info\" | \"warn\" | \"error\"\n\nconst LEVEL_ORDER: Record<Level, number> = { debug: 10, info: 20, warn: 30, error: 40 }\n\nfunction currentLevel(): Level {\n const raw = process.env[ENV.LogLevel]?.toLowerCase() as Level | undefined\n return raw && raw in LEVEL_ORDER ? raw : \"info\"\n}\n\nfunction asJson(): boolean {\n return process.env.WM_LOG_FORMAT === \"json\"\n}\n\nfunction emit(level: Level, message: string, extra?: Record<string, unknown>): void {\n if (LEVEL_ORDER[level] < LEVEL_ORDER[currentLevel()]) return\n\n if (asJson()) {\n const line = JSON.stringify({\n ts: new Date().toISOString(),\n level,\n event: message,\n ...extra,\n })\n process.stderr.write(line + \"\\n\")\n return\n }\n\n const tag =\n level === \"error\"\n ? kleur.red().bold(\"✖\")\n : level === \"warn\"\n ? kleur.yellow().bold(\"!\")\n : level === \"info\"\n ? kleur.cyan().bold(\"›\")\n : kleur.gray(\"·\")\n const detail = extra && Object.keys(extra).length ? \" \" + kleur.gray(JSON.stringify(extra)) : \"\"\n process.stderr.write(`${tag} ${message}${detail}\\n`)\n}\n\nexport const logger = {\n debug: (m: string, e?: Record<string, unknown>) => emit(\"debug\", m, e),\n info: (m: string, e?: Record<string, unknown>) => emit(\"info\", m, e),\n warn: (m: string, e?: Record<string, unknown>) => emit(\"warn\", m, e),\n error: (m: string, e?: Record<string, unknown>) => emit(\"error\", m, e),\n}\n","/**\n * Shared command helpers: resolve config, build client, render output.\n */\nimport type { Command } from \"commander\"\nimport { resolveConfig, type ResolvedConfig } from \"../config.js\"\nimport { WmApiClient } from \"../client.js\"\nimport { logger } from \"../logger.js\"\nimport { WmCliError } from \"../errors.js\"\n\nexport interface GlobalFlags {\n apiUrl?: string\n apiKey?: string\n json?: boolean\n}\n\nexport interface CommandCtx {\n cfg: ResolvedConfig\n client: WmApiClient\n json: boolean\n}\n\nexport function makeCtx(program: Command): CommandCtx {\n // Walk up to the root program so subcommands inherit global flags.\n const root = program.parent ?? program\n const opts = root.opts<GlobalFlags>()\n const cfg = resolveConfig({ apiUrl: opts.apiUrl, apiKey: opts.apiKey })\n return { cfg, client: new WmApiClient(cfg), json: Boolean(opts.json) }\n}\n\nexport function requireAuth(\n ctx: CommandCtx\n): asserts ctx is CommandCtx & { cfg: ResolvedConfig & { apiKey: string } } {\n if (!ctx.cfg.apiKey) throw WmCliError.authRequired()\n}\n\nexport function renderResult(ctx: CommandCtx, payload: unknown, summary?: string): void {\n if (ctx.json) {\n process.stdout.write(JSON.stringify(payload, null, 2) + \"\\n\")\n return\n }\n if (summary) logger.info(summary)\n process.stdout.write(JSON.stringify(payload, null, 2) + \"\\n\")\n}\n","import type { Command } from \"commander\"\nimport { makeCtx, requireAuth, renderResult } from \"./_shared.js\"\n\nexport function registerWhoami(program: Command): void {\n program\n .command(\"whoami\")\n .description(\"Show the logged-in account and remaining credits.\")\n .action(async () => {\n const ctx = makeCtx(program)\n requireAuth(ctx)\n const me = await ctx.client.whoami()\n renderResult(ctx, me, `${me.email} · ${me.creditsRemaining} credits · plan ${me.plan}`)\n })\n}\n","/**\n * `wm gen image|video` — image and video generation.\n *\n * Calls the same REST endpoints `mcp-director` already proxies to:\n * POST /studio/generate-image\n * POST /studio/generate-video\n */\nimport type { Command } from \"commander\"\nimport ora from \"ora\"\nimport { makeCtx, requireAuth, renderResult } from \"./_shared.js\"\nimport { DEFAULT_MODELS } from \"../constants.js\"\nimport { downloadToFile } from \"../util/download.js\"\nimport { awaitJob } from \"../util/await-job.js\"\n\ninterface ImageOpts {\n model?: string\n imageUrl?: string\n aspectRatio?: string\n negativePrompt?: string\n numImages?: string\n seed?: string\n out?: string\n}\n\ninterface VideoOpts {\n model?: string\n image?: string\n duration?: string\n aspectRatio?: string\n out?: string\n}\n\nexport function registerGen(program: Command): void {\n const gen = program.command(\"gen\").description(\"Generate creative assets (image, video).\")\n\n gen\n .command(\"image <prompt>\")\n .description(\"Text-to-image or image-to-image generation.\")\n .option(\"-m, --model <id>\", \"Provider/model id\", DEFAULT_MODELS.image)\n .option(\"-i, --image-url <url>\", \"Reference image URL for img2img variants\")\n .option(\"-a, --aspect-ratio <ratio>\", \"1:1 | 16:9 | 9:16 | 4:3 | 3:4\")\n .option(\"-n, --negative-prompt <text>\", \"Negative prompt\")\n .option(\"--num-images <n>\", \"Batch size\", \"1\")\n .option(\"--seed <n>\", \"Deterministic seed\")\n .option(\"-o, --out <file>\", \"Download the result to this path\")\n .action(async (prompt: string, opts: ImageOpts) => {\n const ctx = makeCtx(program)\n requireAuth(ctx)\n const spinner = ctx.json ? null : ora(\"Generating image…\").start()\n try {\n const submit = await ctx.client.json<Record<string, unknown>>({\n method: \"POST\",\n path: \"/studio/generate-image\",\n body: {\n prompt,\n model: opts.model,\n image_url: opts.imageUrl,\n aspect_ratio: opts.aspectRatio,\n negative_prompt: opts.negativePrompt,\n num_images: opts.numImages ? Number(opts.numImages) : undefined,\n seed: opts.seed ? Number(opts.seed) : undefined,\n },\n })\n if (spinner) spinner.text = \"Rendering image…\"\n const result = await awaitJob(ctx.client, submit, \"image\", (s) => {\n if (spinner) spinner.text = `Image · ${s.status}`\n })\n spinner?.succeed(\"Image ready.\")\n if (opts.out && result.imageUrl) await downloadToFile(result.imageUrl, opts.out)\n renderResult(ctx, result, `→ ${result.imageUrl}`)\n } catch (e) {\n spinner?.fail(\"Image generation failed.\")\n throw e\n }\n })\n\n gen\n .command(\"video <prompt>\")\n .description(\"Text-to-video or image-to-video. Async; polls until done.\")\n .option(\"-m, --model <id>\", \"Provider/model id (auto-picks i2v vs t2v based on --image).\")\n .option(\"-i, --image <url>\", \"Starting frame for image-to-video\")\n .option(\"-d, --duration <seconds>\", \"Clip length\", \"5\")\n .option(\"-a, --aspect-ratio <ratio>\", \"16:9 | 9:16 | 1:1\", \"16:9\")\n .option(\"-o, --out <file>\", \"Download the final clip to this path\")\n .action(async (prompt: string, opts: VideoOpts) => {\n const ctx = makeCtx(program)\n requireAuth(ctx)\n const model =\n opts.model ?? (opts.image ? DEFAULT_MODELS.videoImage : DEFAULT_MODELS.videoText)\n const spinner = ctx.json ? null : ora(\"Submitting video job…\").start()\n try {\n const submit = await ctx.client.json<Record<string, unknown>>({\n method: \"POST\",\n path: \"/studio/generate-video\",\n body: {\n prompt,\n model,\n image_url: opts.image,\n duration: Number(opts.duration),\n aspect_ratio: opts.aspectRatio,\n },\n })\n if (spinner) spinner.text = \"Rendering video…\"\n const final = await awaitJob(ctx.client, submit, \"video\", (s) => {\n if (spinner) spinner.text = `Video · ${s.status}`\n })\n spinner?.succeed(\"Video ready.\")\n if (opts.out && final.videoUrl) await downloadToFile(final.videoUrl, opts.out)\n renderResult(ctx, final, `→ ${final.videoUrl}`)\n } catch (e) {\n spinner?.fail(\"Video generation failed.\")\n throw e\n }\n })\n}\n","/**\n * Stream a remote asset to a local path. Used by `--out` flags.\n */\nimport { createWriteStream } from \"node:fs\"\nimport { mkdir } from \"node:fs/promises\"\nimport { dirname } from \"node:path\"\nimport { pipeline } from \"node:stream/promises\"\nimport { request } from \"undici\"\n\nexport async function downloadToFile(url: string, outPath: string): Promise<void> {\n await mkdir(dirname(outPath), { recursive: true })\n const res = await request(url, { method: \"GET\" })\n if (res.statusCode >= 400) throw new Error(`download failed: HTTP ${res.statusCode}`)\n // undici's res.body is already a Node Readable — pipe it straight.\n await pipeline(res.body, createWriteStream(outPath))\n}\n","/**\n * Generic async-job poller. Used by `gen video` and `campaign` commands.\n */\nimport { DEFAULTS } from \"../constants.js\"\nimport { WmCliError, ExitCode } from \"../errors.js\"\n\nexport interface PollOptions<T> {\n /** Function that fetches the latest status. */\n fetch: () => Promise<T>\n /** Returns true when the job has reached a terminal state. */\n done: (state: T) => boolean\n /** Optional hook fired on each tick (for spinners / progress). */\n onTick?: (state: T) => void\n intervalMs?: number\n timeoutMs?: number\n}\n\nexport async function poll<T>(opts: PollOptions<T>): Promise<T> {\n const interval = opts.intervalMs ?? DEFAULTS.jobPollIntervalMs\n const deadline = Date.now() + (opts.timeoutMs ?? DEFAULTS.jobPollTimeoutMs)\n\n for (;;) {\n const state = await opts.fetch()\n opts.onTick?.(state)\n if (opts.done(state)) return state\n if (Date.now() > deadline) {\n throw new WmCliError({\n code: \"timeout\",\n exitCode: ExitCode.TIMEOUT,\n message: \"Job polling timed out.\",\n })\n }\n await sleep(interval)\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms))\n}\n","/**\n * Handle the queued-job response shape returned by /studio/generate-image,\n * /studio/generate-video and /studio/upscale-image.\n *\n * If the response carries `queued: true`, poll `/studio/jobs/:id` until it\n * reports `status: \"succeeded\"` (and the requested URL field is present)\n * or `status: \"failed\"`. Otherwise, return the response as-is so old/sync\n * code paths keep working.\n */\nimport type { WmApiClient } from \"../client.js\"\nimport { poll } from \"./poll.js\"\nimport { WmCliError, ExitCode } from \"../errors.js\"\n\nexport type Asset = \"image\" | \"video\"\n\ninterface SubmitResponse {\n queued?: boolean\n generationId?: string\n id?: string\n jobId?: string\n imageUrl?: string\n videoUrl?: string\n creditsRemaining?: number\n}\n\ninterface JobStatus {\n jobId: string\n status: \"succeeded\" | \"running\" | \"failed\"\n imageUrl?: string | null\n videoUrl?: string | null\n creditsCharged?: number | null\n}\n\nexport interface AwaitedJob {\n imageUrl?: string\n videoUrl?: string\n creditsRemaining?: number\n jobId?: string\n}\n\nexport async function awaitJob(\n client: WmApiClient,\n submit: SubmitResponse,\n asset: Asset,\n onTick?: (s: JobStatus) => void\n): Promise<AwaitedJob> {\n // Sync response: URL already present.\n if (!submit.queued) {\n const url = asset === \"image\" ? submit.imageUrl : submit.videoUrl\n if (url) {\n return {\n [asset === \"image\" ? \"imageUrl\" : \"videoUrl\"]: url,\n creditsRemaining: submit.creditsRemaining,\n }\n }\n }\n\n const id = submit.generationId ?? submit.id\n if (!id) {\n throw new WmCliError({\n code: \"server\",\n exitCode: ExitCode.SERVER,\n message: \"Server did not return a generationId to poll.\",\n })\n }\n\n const final = await poll<JobStatus>({\n fetch: () => client.json<JobStatus>({ method: \"GET\", path: `/studio/jobs/${id}` }),\n done: (s) => s.status === \"succeeded\" || s.status === \"failed\",\n onTick,\n })\n\n if (final.status !== \"succeeded\") {\n throw new WmCliError({\n code: \"server\",\n exitCode: ExitCode.SERVER,\n message: `Job ${id} finished with status ${final.status}.`,\n })\n }\n\n const url = asset === \"image\" ? final.imageUrl : final.videoUrl\n if (!url) {\n throw new WmCliError({\n code: \"server\",\n exitCode: ExitCode.SERVER,\n message: `Job ${id} succeeded but no ${asset} URL was returned.`,\n })\n }\n\n return {\n [asset === \"image\" ? \"imageUrl\" : \"videoUrl\"]: url,\n jobId: id,\n }\n}\n","import type { Command } from \"commander\"\nimport ora from \"ora\"\nimport { makeCtx, requireAuth, renderResult } from \"./_shared.js\"\nimport { downloadToFile } from \"../util/download.js\"\nimport { awaitJob } from \"../util/await-job.js\"\n\ninterface UpscaleOpts {\n factor?: string\n topazModel?: string\n faceEnhancement?: boolean\n out?: string\n}\n\nexport function registerUpscale(program: Command): void {\n program\n .command(\"upscale <imageUrl>\")\n .description(\"Topaz upscale of an image (factor 1–4).\")\n .option(\"-f, --factor <n>\", \"Upscale factor (1|2|3|4)\", \"2\")\n .option(\"--topaz-model <name>\", \"Topaz preset\", \"Standard V2\")\n .option(\"--no-face-enhancement\", \"Disable Topaz face enhancement\")\n .option(\"-o, --out <file>\", \"Download the result to this path\")\n .action(async (imageUrl: string, opts: UpscaleOpts) => {\n const ctx = makeCtx(program)\n requireAuth(ctx)\n const spinner = ctx.json ? null : ora(\"Upscaling…\").start()\n try {\n const submit = await ctx.client.json<Record<string, unknown>>({\n method: \"POST\",\n path: \"/studio/upscale-image\",\n body: {\n image_url: imageUrl,\n upscale_factor: opts.factor ? Number(opts.factor) : 2,\n topaz_model: opts.topazModel,\n face_enhancement: opts.faceEnhancement,\n },\n })\n const result = await awaitJob(ctx.client, submit, \"image\", (s) => {\n if (spinner) spinner.text = `Upscale · ${s.status}`\n })\n spinner?.succeed(\"Upscaled.\")\n if (opts.out && result.imageUrl) await downloadToFile(result.imageUrl, opts.out)\n renderResult(ctx, result, `→ ${result.imageUrl}`)\n } catch (e) {\n spinner?.fail(\"Upscale failed.\")\n throw e\n }\n })\n}\n","/**\n * `wm campaign` — submit a brief to `director_creative_brief_to_run` or\n * `director_creative_batch_variations`, then poll the resulting run(s).\n */\nimport type { Command } from \"commander\"\nimport ora from \"ora\"\nimport { makeCtx, requireAuth, renderResult } from \"./_shared.js\"\nimport { poll } from \"../util/poll.js\"\n\ninterface CampaignOpts {\n variations?: string\n duration?: string\n platform?: string\n style?: string\n projectId?: string\n follow?: boolean\n}\n\ninterface RunStatus {\n runId: string\n status: string\n masterVideoUrl?: string\n}\n\nexport function registerCampaign(program: Command): void {\n program\n .command(\"campaign <brief>\")\n .description(\n \"Turn a creative brief into a full video run (or N variations) via the director_* pipeline.\"\n )\n .option(\"-n, --variations <n>\", \"Generate N parallel variations (1 = single run)\", \"1\")\n .option(\"-d, --duration <seconds>\", \"Target duration\", \"60\")\n .option(\"-p, --platform <name>\", \"youtube | tiktok | instagram\", \"youtube\")\n .option(\"-s, --style <name>\", \"cinematic | ugc | documentary | …\", \"cinematic\")\n .option(\"--project-id <id>\", \"Existing wmstudio project id (required for variations)\")\n .option(\"--no-follow\", \"Submit and return immediately without polling\")\n .action(async (brief: string, opts: CampaignOpts) => {\n const ctx = makeCtx(program)\n requireAuth(ctx)\n const variations = Math.max(1, Number(opts.variations ?? \"1\"))\n const spinner = ctx.json ? null : ora(\"Submitting brief…\").start()\n\n try {\n const submission =\n variations > 1\n ? await ctx.client.json<{ runIds: string[] }>({\n method: \"POST\",\n path: \"/studio/director/batch-variations\",\n body: {\n base_prompt: brief,\n max_concurrent: variations,\n project_id: opts.projectId,\n },\n })\n : await ctx.client.json<{ runIds: string[] }>({\n method: \"POST\",\n path: \"/studio/director/brief-to-run\",\n body: {\n brief,\n duration_target_seconds: Number(opts.duration),\n platform: opts.platform,\n style: opts.style,\n },\n })\n\n const runIds = submission.runIds ?? []\n if (!opts.follow || runIds.length === 0) {\n spinner?.succeed(`Submitted ${runIds.length} run(s).`)\n renderResult(ctx, submission)\n return\n }\n\n if (spinner) spinner.text = `Polling ${runIds.length} run(s)…`\n const finals = await Promise.all(\n runIds.map((id) =>\n poll<RunStatus>({\n fetch: () => ctx.client.json({ method: \"GET\", path: `/studio/runs/${id}` }),\n done: (s) => [\"succeeded\", \"failed\", \"cancelled\"].includes(s.status),\n })\n )\n )\n spinner?.succeed(\"Run(s) complete.\")\n renderResult(ctx, { runs: finals })\n } catch (e) {\n spinner?.fail(\"Campaign failed.\")\n throw e\n }\n })\n}\n","/**\n * `wm jobs list|get` — inspect async generation jobs and runs.\n */\nimport type { Command } from \"commander\"\nimport { makeCtx, requireAuth, renderResult } from \"./_shared.js\"\n\nexport function registerJobs(program: Command): void {\n const jobs = program.command(\"jobs\").description(\"List and inspect generation jobs.\")\n\n jobs\n .command(\"list\")\n .description(\"List recent jobs (default: 20 most recent).\")\n .option(\"-l, --limit <n>\", \"Number of jobs\", \"20\")\n .option(\"-s, --status <s>\", \"Filter by status (running|succeeded|failed|cancelled)\")\n .action(async (opts: { limit?: string; status?: string }) => {\n const ctx = makeCtx(program)\n requireAuth(ctx)\n const data = await ctx.client.json({\n method: \"GET\",\n path: \"/studio/jobs\",\n query: { limit: opts.limit, status: opts.status },\n })\n renderResult(ctx, data)\n })\n\n jobs\n .command(\"get <jobId>\")\n .description(\"Fetch a single job by id.\")\n .action(async (jobId: string) => {\n const ctx = makeCtx(program)\n requireAuth(ctx)\n const data = await ctx.client.json({ method: \"GET\", path: `/studio/jobs/${jobId}` })\n renderResult(ctx, data)\n })\n}\n","// Entrypoint. tsup prepends the `#!/usr/bin/env node` shebang in the build banner.\nimport { run } from \"./cli.js\"\nimport { WmCliError, ExitCode } from \"./errors.js\"\nimport { logger } from \"./logger.js\"\n\nrun(process.argv).catch((err: unknown) => {\n if (err instanceof WmCliError) {\n logger.error(err.message, err.details)\n process.exit(err.exitCode)\n }\n const message = err instanceof Error ? err.message : String(err)\n logger.error(`unexpected error: ${message}`)\n process.exit(ExitCode.UNEXPECTED)\n})\n"],"mappings":"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","names":["z","z","dirname","url","ora","ora","ora","ora"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/commands/login.ts","../src/config.ts","../src/constants.ts","../src/client.ts","../src/errors.ts","../src/logger.ts","../src/commands/_shared.ts","../src/commands/whoami.ts","../src/commands/gen.ts","../src/util/download.ts","../src/util/poll.ts","../src/util/await-job.ts","../src/commands/upscale.ts","../src/commands/campaign.ts","../src/commands/jobs.ts","../src/index.ts"],"sourcesContent":["/**\n * Commander root. Each subcommand lives in its own file under ./commands.\n */\nimport { Command } from \"commander\"\nimport { registerLogin } from \"./commands/login.js\"\nimport { registerWhoami } from \"./commands/whoami.js\"\nimport { registerGen } from \"./commands/gen.js\"\nimport { registerUpscale } from \"./commands/upscale.js\"\nimport { registerCampaign } from \"./commands/campaign.js\"\nimport { registerJobs } from \"./commands/jobs.js\"\n\nexport async function run(argv: string[]): Promise<void> {\n const program = new Command()\n .name(\"wm\")\n .description(\n \"WM Studio command-line client. Generate images, videos, brand campaigns, and 3D assets.\"\n )\n .version(VERSION, \"-v, --version\")\n .option(\"--api-url <url>\", \"Override WM Studio API base URL\")\n .option(\"--api-key <key>\", \"Override API key (otherwise WM_API_KEY or ~/.wm/config.json)\")\n .option(\"--json\", \"Emit machine-readable JSON output\", false)\n .showHelpAfterError(\"(run `wm <command> --help` for details)\")\n\n registerLogin(program)\n registerWhoami(program)\n registerGen(program)\n registerUpscale(program)\n registerCampaign(program)\n registerJobs(program)\n\n await program.parseAsync(argv)\n}\n\n// Injected at build time by tsup via `define` — fallback for `tsx`/`vitest`.\ndeclare const __VERSION__: string | undefined\nconst VERSION: string =\n (typeof __VERSION__ !== \"undefined\" && __VERSION__) ||\n process.env.npm_package_version ||\n \"0.0.0-dev\"\n","import type { Command } from \"commander\"\nimport { password } from \"@inquirer/prompts\"\nimport kleur from \"kleur\"\nimport { writeFileConfig } from \"../config.js\"\nimport { WmApiClient } from \"../client.js\"\nimport { resolveConfig } from \"../config.js\"\nimport { logger } from \"../logger.js\"\nimport { WmCliError } from \"../errors.js\"\nimport { DEFAULTS } from \"../constants.js\"\n\nexport function registerLogin(program: Command): void {\n program\n .command(\"login\")\n .description(\"Save your WM Studio API key to ~/.wm/config.json (chmod 0600).\")\n .option(\"--key <apiKey>\", \"Pass the API key non-interactively (useful in CI).\")\n .action(async (opts: { key?: string }) => {\n if (!opts.key) {\n process.stderr.write(\n `\\n${kleur.bold(\"Get an API key:\")} ${kleur.cyan(DEFAULTS.apiKeysUrl)}\\n` +\n kleur.dim(\" Sign in → Dashboard → API keys → Create new key, then paste below.\\n\\n\")\n )\n }\n const apiKey =\n opts.key ??\n (await password({\n message: \"Paste your WM Studio API key:\",\n mask: \"*\",\n }))\n if (!apiKey || apiKey.trim().length === 0) {\n throw WmCliError.usage(\"API key cannot be empty.\")\n }\n\n const cfg = resolveConfig({ apiKey: apiKey.trim() })\n const client = new WmApiClient(cfg)\n const me = await client.whoami()\n\n writeFileConfig({ apiKey: apiKey.trim() })\n logger.info(`Logged in as ${me.email} (${me.creditsRemaining} credits remaining).`)\n })\n}\n","/**\n * On-disk config + env merge.\n *\n * Precedence (highest first):\n * 1. CLI flag (--api-url, --api-key)\n * 2. Environment variables (WMSTUDIO_API_URL, WM_API_KEY)\n * 3. ~/.wm/config.json\n * 4. Built-in defaults (constants.ts)\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport { dirname, join } from \"node:path\"\nimport { z } from \"zod\"\nimport { DEFAULTS, ENV } from \"./constants.js\"\n\nconst FileConfigSchema = z.object({\n apiUrl: z.string().url().optional(),\n apiKey: z.string().min(1).optional(),\n uploadUrl: z.string().url().optional(),\n upgradeUrl: z.string().url().optional(),\n lowCreditsThreshold: z.number().int().nonnegative().optional(),\n})\n\nexport type FileConfig = z.infer<typeof FileConfigSchema>\n\nexport interface ResolvedConfig {\n apiUrl: string\n apiKey: string | undefined\n uploadUrl: string\n upgradeUrl: string\n lowCreditsThreshold: number\n configPath: string\n}\n\nexport function configDir(): string {\n const override = process.env[ENV.ConfigDir]\n return override && override.length > 0 ? override : join(homedir(), \".wm\")\n}\n\nexport function configPath(): string {\n return join(configDir(), \"config.json\")\n}\n\nexport function readFileConfig(): FileConfig {\n const path = configPath()\n if (!existsSync(path)) return {}\n try {\n const raw = readFileSync(path, \"utf8\")\n return FileConfigSchema.parse(JSON.parse(raw))\n } catch {\n // Corrupt config — return empty rather than crash the CLI on every command.\n return {}\n }\n}\n\nexport function writeFileConfig(patch: FileConfig): void {\n const merged = { ...readFileConfig(), ...patch }\n const dir = configDir()\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 0o700 })\n const file = configPath()\n writeFileSync(file, JSON.stringify(merged, null, 2) + \"\\n\", { mode: 0o600 })\n // Defensive: enforce 0600 even if file pre-existed.\n try {\n chmodSync(file, 0o600)\n } catch {\n /* non-fatal */\n }\n // And 0700 on the dir.\n try {\n chmodSync(dirname(file), 0o700)\n } catch {\n /* non-fatal */\n }\n}\n\nexport interface ConfigOverrides {\n apiUrl?: string\n apiKey?: string\n}\n\nexport function resolveConfig(overrides: ConfigOverrides = {}): ResolvedConfig {\n const file = readFileConfig()\n return {\n apiUrl: overrides.apiUrl ?? process.env[ENV.ApiUrl] ?? file.apiUrl ?? DEFAULTS.apiUrl,\n apiKey: overrides.apiKey ?? process.env[ENV.ApiKey] ?? file.apiKey,\n uploadUrl: process.env[ENV.UploadUrl] ?? file.uploadUrl ?? DEFAULTS.uploadUrl,\n upgradeUrl: process.env[ENV.UpgradeUrl] ?? file.upgradeUrl ?? DEFAULTS.upgradeUrl,\n lowCreditsThreshold:\n numFromEnv(process.env[ENV.LowCreditsThreshold]) ??\n file.lowCreditsThreshold ??\n DEFAULTS.lowCreditsThreshold,\n configPath: configPath(),\n }\n}\n\nfunction numFromEnv(raw: string | undefined): number | undefined {\n if (!raw) return undefined\n const n = Number(raw)\n return Number.isFinite(n) ? n : undefined\n}\n","/**\n * Shared constants. Keep in sync with `mcp-director/src/config.py`.\n * Anything that names an env var, an endpoint, or a model id belongs here.\n */\n\nexport const DEFAULTS = {\n /** Hosted WM Studio REST API base. Override via `WMSTUDIO_API_URL`. */\n apiUrl: \"https://wmstudio.io/api\",\n /** Page surfaced when the API requires a public asset URL. */\n uploadUrl: \"https://wmstudio.io/dashboard/uploads\",\n /** Credit top-up landing page. */\n upgradeUrl: \"https://wmstudio.io/dashboard/credits\",\n /** Where users create API keys. Shown by `wm login`. */\n apiKeysUrl: \"https://wmstudio.io/dashboard/api-keys\",\n /** Below this remaining-credit count we surface a warning. */\n lowCreditsThreshold: 50,\n /** Default request timeout (ms) for non-job calls. */\n requestTimeoutMs: 60_000,\n /** Polling interval (ms) for async jobs. */\n jobPollIntervalMs: 4_000,\n /** Hard ceiling for job polling (ms). */\n jobPollTimeoutMs: 15 * 60 * 1000,\n} as const\n\nexport const ENV = {\n ApiUrl: \"WMSTUDIO_API_URL\",\n ApiKey: \"WM_API_KEY\",\n UploadUrl: \"ASSET_UPLOAD_URL\",\n UpgradeUrl: \"CREDITS_UPGRADE_URL\",\n LowCreditsThreshold: \"CREDITS_LOW_THRESHOLD\",\n LogLevel: \"WM_LOG_LEVEL\",\n ConfigDir: \"WM_CONFIG_DIR\",\n} as const\n\n/** Default model ids per tool — mirror `mcp-director/src/tools/studio.py` defaults. */\nexport const DEFAULT_MODELS = {\n image: \"fal-ai/flux/dev\",\n videoText: \"fal-ai/kling-video/v2.5-turbo/pro/text-to-video\",\n videoImage: \"fal-ai/kling-video/v2.5-turbo/pro/image-to-video\",\n upscaleImage: \"fal-ai/topaz/upscale/image\",\n upscaleVideo: \"fal-ai/topaz/upscale/video\",\n threeD: \"fal-ai/meshy/v6/image-to-3d\",\n brandshot: \"fal-ai/flux/dev\",\n cameraAngles: \"fal-ai/flux/dev\",\n casting: \"fal-ai/flux/dev\",\n ugcRoom: \"fal-ai/flux/dev\",\n} as const\n","/**\n * Thin HTTP client for the WM Studio REST API.\n *\n * Endpoints mirror what `mcp-director/src/wmstudio_client.py` already calls\n * — keeping the contract identical means the CLI is a drop-in alternative\n * to the MCP for the same underlying backend.\n */\nimport { z } from \"zod\"\nimport { DEFAULTS } from \"./constants.js\"\nimport { ExitCode, WmCliError } from \"./errors.js\"\nimport type { ResolvedConfig } from \"./config.js\"\n\nconst ApiErrorSchema = z.object({\n code: z.string().optional(),\n error: z.string().optional(),\n message: z.string().optional(),\n requiresTopUp: z.boolean().optional(),\n upgradeUrl: z.string().url().optional(),\n uploadUrl: z.string().url().optional(),\n})\n\nexport interface JsonRequest {\n method: \"GET\" | \"POST\" | \"PATCH\" | \"DELETE\"\n path: string\n body?: Record<string, unknown>\n query?: Record<string, string | number | boolean | undefined>\n timeoutMs?: number\n}\n\nexport class WmApiClient {\n constructor(private readonly cfg: ResolvedConfig) {}\n\n /** GET /me — returns account + credit balance. */\n whoami(): Promise<{\n userId: string\n email: string\n creditsRemaining: number\n plan: string\n }> {\n return this.json({ method: \"GET\", path: \"/me\" })\n }\n\n /** Validate the current api key without side-effects. */\n async ping(): Promise<boolean> {\n try {\n await this.whoami()\n return true\n } catch (err) {\n if (err instanceof WmCliError && err.code === \"auth_invalid\") return false\n throw err\n }\n }\n\n /** Generic JSON helper. */\n async json<T = unknown>(req: JsonRequest): Promise<T> {\n if (!this.cfg.apiKey) throw WmCliError.authRequired()\n\n const url = new URL(req.path.replace(/^\\//, \"\"), this.cfg.apiUrl.replace(/\\/?$/, \"/\"))\n if (req.query) {\n for (const [k, v] of Object.entries(req.query)) {\n if (v !== undefined) url.searchParams.set(k, String(v))\n }\n }\n\n const ac = new AbortController()\n const timeoutMs = req.timeoutMs ?? DEFAULTS.requestTimeoutMs\n const timeout = setTimeout(() => ac.abort(), timeoutMs)\n\n let res: Response\n try {\n res = await fetch(url, {\n method: req.method,\n headers: {\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n authorization: `Bearer ${this.cfg.apiKey}`,\n \"user-agent\": userAgent(),\n },\n body: req.body ? JSON.stringify(req.body) : undefined,\n signal: ac.signal,\n })\n } catch (err) {\n clearTimeout(timeout)\n if (err instanceof WmCliError) throw err\n const aborted = (err as { name?: string } | null)?.name === \"AbortError\"\n throw new WmCliError({\n code: aborted ? \"timeout\" : \"network\",\n exitCode: aborted ? ExitCode.TIMEOUT : ExitCode.NETWORK,\n message: aborted\n ? `Request timed out after ${timeoutMs}ms (${req.method} ${req.path})`\n : `Network error: ${(err as Error).message}`,\n details: { cause: (err as Error).message },\n })\n }\n clearTimeout(timeout)\n\n const text = await res.text()\n const parsed: unknown = text ? safeJson(text) : undefined\n\n if (res.status >= 200 && res.status < 300) {\n return parsed as T\n }\n throw classify(res.status, parsed, this.cfg)\n }\n}\n\nfunction safeJson(text: string): unknown {\n try {\n return JSON.parse(text)\n } catch {\n return { raw: text }\n }\n}\n\nfunction classify(status: number, payload: unknown, cfg: ResolvedConfig): WmCliError {\n const parsed = ApiErrorSchema.safeParse(payload)\n const data = parsed.success ? parsed.data : {}\n const msg = data.message ?? data.error ?? `HTTP ${status}`\n\n if (status === 401 || status === 403) return WmCliError.authInvalid(msg)\n if (status === 402 || data.requiresTopUp) {\n return WmCliError.upgradeRequired(data.upgradeUrl ?? cfg.upgradeUrl, msg)\n }\n if (status === 422 && data.code === \"asset_url_required\") {\n return WmCliError.assetUrlRequired(data.uploadUrl ?? cfg.uploadUrl, msg)\n }\n return new WmCliError({\n code: status >= 500 ? \"server\" : \"usage\",\n exitCode: status >= 500 ? 50 : 2,\n message: msg,\n details: { status, payload },\n })\n}\n\nfunction userAgent(): string {\n return `wm-cli/${process.env.npm_package_version ?? \"dev\"} (node ${process.version})`\n}\n","/**\n * Error model + exit codes for `wm`.\n *\n * Exit codes are stable contract — kept in sync with `mcp-director` error codes\n * (see ../docs/CONVENTIONS.md). Used by CI, shell scripts, and humans.\n */\n\nexport const ExitCode = {\n OK: 0,\n USAGE: 2, // bad arguments / unknown command\n AUTH_REQUIRED: 10, // not logged in / missing api key\n AUTH_INVALID: 11, // server rejected token (401/403)\n ASSET_URL_REQUIRED: 20, // missing or unreachable user-provided URL\n UPGRADE_REQUIRED: 30, // 402 insufficient credits\n RATE_LIMITED: 31, // 429\n NETWORK: 40, // transport / DNS / TLS\n SERVER: 50, // 5xx\n TIMEOUT: 51, // server-side or client-side deadline exceeded\n UNEXPECTED: 99, // crashed / not classifiable\n} as const\n\nexport type ExitCodeValue = (typeof ExitCode)[keyof typeof ExitCode]\n\n/**\n * The canonical error codes returned by the WM Studio API and `mcp-director`.\n * Keeping the same vocabulary across CLI / MCP / SDK is the whole point.\n */\nexport const ErrorCode = {\n AuthRequired: \"auth_required\",\n AuthInvalid: \"auth_invalid\",\n AssetUrlRequired: \"asset_url_required\",\n UpgradeRequired: \"upgrade_required\",\n RateLimited: \"rate_limited\",\n Network: \"network\",\n Server: \"server\",\n Timeout: \"timeout\",\n Usage: \"usage\",\n Unexpected: \"unexpected\",\n} as const\n\nexport type ErrorCodeValue = (typeof ErrorCode)[keyof typeof ErrorCode]\n\nexport class WmCliError extends Error {\n readonly code: ErrorCodeValue\n readonly exitCode: ExitCodeValue\n readonly details?: Record<string, unknown>\n\n constructor(opts: {\n code: ErrorCodeValue\n exitCode: ExitCodeValue\n message: string\n details?: Record<string, unknown>\n }) {\n super(opts.message)\n this.name = \"WmCliError\"\n this.code = opts.code\n this.exitCode = opts.exitCode\n this.details = opts.details\n }\n\n static authRequired(message = \"Not logged in. Run `wm login` first.\"): WmCliError {\n return new WmCliError({\n code: ErrorCode.AuthRequired,\n exitCode: ExitCode.AUTH_REQUIRED,\n message,\n })\n }\n\n static authInvalid(message = \"Invalid or expired API key.\"): WmCliError {\n return new WmCliError({\n code: ErrorCode.AuthInvalid,\n exitCode: ExitCode.AUTH_INVALID,\n message,\n })\n }\n\n static assetUrlRequired(uploadUrl: string, message?: string): WmCliError {\n return new WmCliError({\n code: ErrorCode.AssetUrlRequired,\n exitCode: ExitCode.ASSET_URL_REQUIRED,\n message: message ?? `A real public asset URL is required. Upload at ${uploadUrl}.`,\n details: { uploadUrl },\n })\n }\n\n static upgradeRequired(upgradeUrl: string, message?: string): WmCliError {\n return new WmCliError({\n code: ErrorCode.UpgradeRequired,\n exitCode: ExitCode.UPGRADE_REQUIRED,\n message: message ?? `Insufficient credits. Top up at ${upgradeUrl} and re-run.`,\n details: { upgradeUrl },\n })\n }\n\n static usage(message: string): WmCliError {\n return new WmCliError({\n code: ErrorCode.Usage,\n exitCode: ExitCode.USAGE,\n message,\n })\n }\n}\n","/**\n * Tiny structured logger. Console output is colourised for humans (stderr);\n * when `WM_LOG_FORMAT=json` is set, emits JSON lines compatible with the same\n * shape used by `mcp-director` (`event`, `level`, `ts`, …extra).\n */\nimport kleur from \"kleur\"\nimport { ENV } from \"./constants.js\"\n\ntype Level = \"debug\" | \"info\" | \"warn\" | \"error\"\n\nconst LEVEL_ORDER: Record<Level, number> = { debug: 10, info: 20, warn: 30, error: 40 }\n\nfunction currentLevel(): Level {\n const raw = process.env[ENV.LogLevel]?.toLowerCase() as Level | undefined\n return raw && raw in LEVEL_ORDER ? raw : \"info\"\n}\n\nfunction asJson(): boolean {\n return process.env.WM_LOG_FORMAT === \"json\"\n}\n\nfunction emit(level: Level, message: string, extra?: Record<string, unknown>): void {\n if (LEVEL_ORDER[level] < LEVEL_ORDER[currentLevel()]) return\n\n if (asJson()) {\n const line = JSON.stringify({\n ts: new Date().toISOString(),\n level,\n event: message,\n ...extra,\n })\n process.stderr.write(line + \"\\n\")\n return\n }\n\n const tag =\n level === \"error\"\n ? kleur.red().bold(\"✖\")\n : level === \"warn\"\n ? kleur.yellow().bold(\"!\")\n : level === \"info\"\n ? kleur.cyan().bold(\"›\")\n : kleur.gray(\"·\")\n const detail = extra && Object.keys(extra).length ? \" \" + kleur.gray(JSON.stringify(extra)) : \"\"\n process.stderr.write(`${tag} ${message}${detail}\\n`)\n}\n\nexport const logger = {\n debug: (m: string, e?: Record<string, unknown>) => emit(\"debug\", m, e),\n info: (m: string, e?: Record<string, unknown>) => emit(\"info\", m, e),\n warn: (m: string, e?: Record<string, unknown>) => emit(\"warn\", m, e),\n error: (m: string, e?: Record<string, unknown>) => emit(\"error\", m, e),\n}\n","/**\n * Shared command helpers: resolve config, build client, render output.\n */\nimport type { Command } from \"commander\"\nimport { resolveConfig, type ResolvedConfig } from \"../config.js\"\nimport { WmApiClient } from \"../client.js\"\nimport { logger } from \"../logger.js\"\nimport { WmCliError } from \"../errors.js\"\n\nexport interface GlobalFlags {\n apiUrl?: string\n apiKey?: string\n json?: boolean\n}\n\nexport interface CommandCtx {\n cfg: ResolvedConfig\n client: WmApiClient\n json: boolean\n}\n\nexport function makeCtx(program: Command): CommandCtx {\n // Walk up to the root program so subcommands inherit global flags.\n const root = program.parent ?? program\n const opts = root.opts<GlobalFlags>()\n const cfg = resolveConfig({ apiUrl: opts.apiUrl, apiKey: opts.apiKey })\n return { cfg, client: new WmApiClient(cfg), json: Boolean(opts.json) }\n}\n\nexport function requireAuth(\n ctx: CommandCtx\n): asserts ctx is CommandCtx & { cfg: ResolvedConfig & { apiKey: string } } {\n if (!ctx.cfg.apiKey) throw WmCliError.authRequired()\n}\n\nexport function renderResult(ctx: CommandCtx, payload: unknown, summary?: string): void {\n if (ctx.json) {\n process.stdout.write(JSON.stringify(payload, null, 2) + \"\\n\")\n return\n }\n if (summary) logger.info(summary)\n process.stdout.write(JSON.stringify(payload, null, 2) + \"\\n\")\n}\n","import type { Command } from \"commander\"\nimport { makeCtx, requireAuth, renderResult } from \"./_shared.js\"\n\nexport function registerWhoami(program: Command): void {\n program\n .command(\"whoami\")\n .description(\"Show the logged-in account and remaining credits.\")\n .action(async () => {\n const ctx = makeCtx(program)\n requireAuth(ctx)\n const me = await ctx.client.whoami()\n renderResult(ctx, me, `${me.email} · ${me.creditsRemaining} credits · plan ${me.plan}`)\n })\n}\n","/**\n * `wm gen image|video` — image and video generation.\n *\n * Calls the same REST endpoints `mcp-director` already proxies to:\n * POST /studio/generate-image\n * POST /studio/generate-video\n */\nimport type { Command } from \"commander\"\nimport ora from \"ora\"\nimport { makeCtx, requireAuth, renderResult } from \"./_shared.js\"\nimport { DEFAULT_MODELS } from \"../constants.js\"\nimport { downloadToFile } from \"../util/download.js\"\nimport { awaitJob } from \"../util/await-job.js\"\n\ninterface ImageOpts {\n model?: string\n imageUrl?: string\n aspectRatio?: string\n negativePrompt?: string\n numImages?: string\n seed?: string\n out?: string\n}\n\ninterface VideoOpts {\n model?: string\n image?: string\n duration?: string\n aspectRatio?: string\n out?: string\n}\n\nexport function registerGen(program: Command): void {\n const gen = program.command(\"gen\").description(\"Generate creative assets (image, video).\")\n\n gen\n .command(\"image <prompt>\")\n .description(\"Text-to-image or image-to-image generation.\")\n .option(\"-m, --model <id>\", \"Provider/model id\", DEFAULT_MODELS.image)\n .option(\"-i, --image-url <url>\", \"Reference image URL for img2img variants\")\n .option(\"-a, --aspect-ratio <ratio>\", \"1:1 | 16:9 | 9:16 | 4:3 | 3:4\")\n .option(\"-n, --negative-prompt <text>\", \"Negative prompt\")\n .option(\"--num-images <n>\", \"Batch size\", \"1\")\n .option(\"--seed <n>\", \"Deterministic seed\")\n .option(\"-o, --out <file>\", \"Download the result to this path\")\n .action(async (prompt: string, opts: ImageOpts) => {\n const ctx = makeCtx(program)\n requireAuth(ctx)\n const spinner = ctx.json ? null : ora(\"Generating image…\").start()\n try {\n const submit = await ctx.client.json<Record<string, unknown>>({\n method: \"POST\",\n path: \"/studio/generate-image\",\n body: {\n prompt,\n model: opts.model,\n image_url: opts.imageUrl,\n aspect_ratio: opts.aspectRatio,\n negative_prompt: opts.negativePrompt,\n num_images: opts.numImages ? Number(opts.numImages) : undefined,\n seed: opts.seed ? Number(opts.seed) : undefined,\n },\n })\n if (spinner) spinner.text = \"Rendering image…\"\n const result = await awaitJob(ctx.client, submit, \"image\", (s) => {\n if (spinner) spinner.text = `Image · ${s.status}`\n })\n spinner?.succeed(\"Image ready.\")\n if (opts.out && result.imageUrl) await downloadToFile(result.imageUrl, opts.out)\n renderResult(ctx, result, `→ ${result.imageUrl}`)\n } catch (e) {\n spinner?.fail(\"Image generation failed.\")\n throw e\n }\n })\n\n gen\n .command(\"video <prompt>\")\n .description(\"Text-to-video or image-to-video. Async; polls until done.\")\n .option(\"-m, --model <id>\", \"Provider/model id (auto-picks i2v vs t2v based on --image).\")\n .option(\"-i, --image <url>\", \"Starting frame for image-to-video\")\n .option(\"-d, --duration <seconds>\", \"Clip length\", \"5\")\n .option(\"-a, --aspect-ratio <ratio>\", \"16:9 | 9:16 | 1:1\", \"16:9\")\n .option(\"-o, --out <file>\", \"Download the final clip to this path\")\n .action(async (prompt: string, opts: VideoOpts) => {\n const ctx = makeCtx(program)\n requireAuth(ctx)\n const model =\n opts.model ?? (opts.image ? DEFAULT_MODELS.videoImage : DEFAULT_MODELS.videoText)\n const spinner = ctx.json ? null : ora(\"Submitting video job…\").start()\n try {\n const submit = await ctx.client.json<Record<string, unknown>>({\n method: \"POST\",\n path: \"/studio/generate-video\",\n body: {\n prompt,\n model,\n image_url: opts.image,\n duration: Number(opts.duration),\n aspect_ratio: opts.aspectRatio,\n },\n })\n if (spinner) spinner.text = \"Rendering video…\"\n const final = await awaitJob(ctx.client, submit, \"video\", (s) => {\n if (spinner) spinner.text = `Video · ${s.status}`\n })\n spinner?.succeed(\"Video ready.\")\n if (opts.out && final.videoUrl) await downloadToFile(final.videoUrl, opts.out)\n renderResult(ctx, final, `→ ${final.videoUrl}`)\n } catch (e) {\n spinner?.fail(\"Video generation failed.\")\n throw e\n }\n })\n}\n","/**\n * Stream a remote asset to a local path. Used by `--out` flags.\n */\nimport { createWriteStream } from \"node:fs\"\nimport { mkdir } from \"node:fs/promises\"\nimport { dirname } from \"node:path\"\nimport { pipeline } from \"node:stream/promises\"\nimport { request } from \"undici\"\n\nexport async function downloadToFile(url: string, outPath: string): Promise<void> {\n await mkdir(dirname(outPath), { recursive: true })\n const res = await request(url, { method: \"GET\" })\n if (res.statusCode >= 400) throw new Error(`download failed: HTTP ${res.statusCode}`)\n // undici's res.body is already a Node Readable — pipe it straight.\n await pipeline(res.body, createWriteStream(outPath))\n}\n","/**\n * Generic async-job poller. Used by `gen video` and `campaign` commands.\n */\nimport { DEFAULTS } from \"../constants.js\"\nimport { WmCliError, ExitCode } from \"../errors.js\"\n\nexport interface PollOptions<T> {\n /** Function that fetches the latest status. */\n fetch: () => Promise<T>\n /** Returns true when the job has reached a terminal state. */\n done: (state: T) => boolean\n /** Optional hook fired on each tick (for spinners / progress). */\n onTick?: (state: T) => void\n intervalMs?: number\n timeoutMs?: number\n}\n\nexport async function poll<T>(opts: PollOptions<T>): Promise<T> {\n const interval = opts.intervalMs ?? DEFAULTS.jobPollIntervalMs\n const deadline = Date.now() + (opts.timeoutMs ?? DEFAULTS.jobPollTimeoutMs)\n\n for (;;) {\n const state = await opts.fetch()\n opts.onTick?.(state)\n if (opts.done(state)) return state\n if (Date.now() > deadline) {\n throw new WmCliError({\n code: \"timeout\",\n exitCode: ExitCode.TIMEOUT,\n message: \"Job polling timed out.\",\n })\n }\n await sleep(interval)\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms))\n}\n","/**\n * Handle the queued-job response shape returned by /studio/generate-image,\n * /studio/generate-video and /studio/upscale-image.\n *\n * If the response carries `queued: true`, poll `/studio/jobs/:id` until it\n * reports `status: \"succeeded\"` (and the requested URL field is present)\n * or `status: \"failed\"`. Otherwise, return the response as-is so old/sync\n * code paths keep working.\n */\nimport type { WmApiClient } from \"../client.js\"\nimport { poll } from \"./poll.js\"\nimport { WmCliError, ExitCode } from \"../errors.js\"\n\nexport type Asset = \"image\" | \"video\"\n\ninterface SubmitResponse {\n queued?: boolean\n generationId?: string\n id?: string\n jobId?: string\n imageUrl?: string\n videoUrl?: string\n creditsRemaining?: number\n}\n\ninterface JobStatus {\n jobId: string\n status: \"succeeded\" | \"running\" | \"failed\"\n imageUrl?: string | null\n videoUrl?: string | null\n creditsCharged?: number | null\n}\n\nexport interface AwaitedJob {\n imageUrl?: string\n videoUrl?: string\n creditsRemaining?: number\n jobId?: string\n}\n\nexport async function awaitJob(\n client: WmApiClient,\n submit: SubmitResponse,\n asset: Asset,\n onTick?: (s: JobStatus) => void\n): Promise<AwaitedJob> {\n // Sync response: URL already present.\n if (!submit.queued) {\n const url = asset === \"image\" ? submit.imageUrl : submit.videoUrl\n if (url) {\n return {\n [asset === \"image\" ? \"imageUrl\" : \"videoUrl\"]: url,\n creditsRemaining: submit.creditsRemaining,\n }\n }\n }\n\n const id = submit.generationId ?? submit.id\n if (!id) {\n throw new WmCliError({\n code: \"server\",\n exitCode: ExitCode.SERVER,\n message: \"Server did not return a generationId to poll.\",\n })\n }\n\n const final = await poll<JobStatus>({\n fetch: () => client.json<JobStatus>({ method: \"GET\", path: `/studio/jobs/${id}` }),\n done: (s) => s.status === \"succeeded\" || s.status === \"failed\",\n onTick,\n })\n\n if (final.status !== \"succeeded\") {\n throw new WmCliError({\n code: \"server\",\n exitCode: ExitCode.SERVER,\n message: `Job ${id} finished with status ${final.status}.`,\n })\n }\n\n const url = asset === \"image\" ? final.imageUrl : final.videoUrl\n if (!url) {\n throw new WmCliError({\n code: \"server\",\n exitCode: ExitCode.SERVER,\n message: `Job ${id} succeeded but no ${asset} URL was returned.`,\n })\n }\n\n return {\n [asset === \"image\" ? \"imageUrl\" : \"videoUrl\"]: url,\n jobId: id,\n }\n}\n","import type { Command } from \"commander\"\nimport ora from \"ora\"\nimport { makeCtx, requireAuth, renderResult } from \"./_shared.js\"\nimport { downloadToFile } from \"../util/download.js\"\nimport { awaitJob } from \"../util/await-job.js\"\n\ninterface UpscaleOpts {\n factor?: string\n topazModel?: string\n faceEnhancement?: boolean\n out?: string\n}\n\nexport function registerUpscale(program: Command): void {\n program\n .command(\"upscale <imageUrl>\")\n .description(\"Topaz upscale of an image (factor 1–4).\")\n .option(\"-f, --factor <n>\", \"Upscale factor (1|2|3|4)\", \"2\")\n .option(\"--topaz-model <name>\", \"Topaz preset\", \"Standard V2\")\n .option(\"--no-face-enhancement\", \"Disable Topaz face enhancement\")\n .option(\"-o, --out <file>\", \"Download the result to this path\")\n .action(async (imageUrl: string, opts: UpscaleOpts) => {\n const ctx = makeCtx(program)\n requireAuth(ctx)\n const spinner = ctx.json ? null : ora(\"Upscaling…\").start()\n try {\n const submit = await ctx.client.json<Record<string, unknown>>({\n method: \"POST\",\n path: \"/studio/upscale-image\",\n body: {\n image_url: imageUrl,\n upscale_factor: opts.factor ? Number(opts.factor) : 2,\n topaz_model: opts.topazModel,\n face_enhancement: opts.faceEnhancement,\n },\n })\n const result = await awaitJob(ctx.client, submit, \"image\", (s) => {\n if (spinner) spinner.text = `Upscale · ${s.status}`\n })\n spinner?.succeed(\"Upscaled.\")\n if (opts.out && result.imageUrl) await downloadToFile(result.imageUrl, opts.out)\n renderResult(ctx, result, `→ ${result.imageUrl}`)\n } catch (e) {\n spinner?.fail(\"Upscale failed.\")\n throw e\n }\n })\n}\n","/**\n * `wm campaign` — submit a brief to `director_creative_brief_to_run` or\n * `director_creative_batch_variations`, then poll the resulting run(s).\n */\nimport type { Command } from \"commander\"\nimport ora from \"ora\"\nimport { makeCtx, requireAuth, renderResult } from \"./_shared.js\"\nimport { poll } from \"../util/poll.js\"\n\ninterface CampaignOpts {\n variations?: string\n duration?: string\n platform?: string\n style?: string\n projectId?: string\n follow?: boolean\n}\n\ninterface RunStatus {\n runId: string\n status: string\n masterVideoUrl?: string\n}\n\nexport function registerCampaign(program: Command): void {\n program\n .command(\"campaign <brief>\")\n .description(\n \"Turn a creative brief into a full video run (or N variations) via the director_* pipeline.\"\n )\n .option(\"-n, --variations <n>\", \"Generate N parallel variations (1 = single run)\", \"1\")\n .option(\"-d, --duration <seconds>\", \"Target duration\", \"60\")\n .option(\"-p, --platform <name>\", \"youtube | tiktok | instagram\", \"youtube\")\n .option(\"-s, --style <name>\", \"cinematic | ugc | documentary | …\", \"cinematic\")\n .option(\"--project-id <id>\", \"Existing wmstudio project id (required for variations)\")\n .option(\"--no-follow\", \"Submit and return immediately without polling\")\n .action(async (brief: string, opts: CampaignOpts) => {\n const ctx = makeCtx(program)\n requireAuth(ctx)\n const variations = Math.max(1, Number(opts.variations ?? \"1\"))\n const spinner = ctx.json ? null : ora(\"Submitting brief…\").start()\n\n try {\n const submission =\n variations > 1\n ? await ctx.client.json<{ runIds: string[] }>({\n method: \"POST\",\n path: \"/studio/director/batch-variations\",\n body: {\n base_prompt: brief,\n max_concurrent: variations,\n project_id: opts.projectId,\n },\n })\n : await ctx.client.json<{ runIds: string[] }>({\n method: \"POST\",\n path: \"/studio/director/brief-to-run\",\n body: {\n brief,\n duration_target_seconds: Number(opts.duration),\n platform: opts.platform,\n style: opts.style,\n },\n })\n\n const runIds = submission.runIds ?? []\n if (!opts.follow || runIds.length === 0) {\n spinner?.succeed(`Submitted ${runIds.length} run(s).`)\n renderResult(ctx, submission)\n return\n }\n\n if (spinner) spinner.text = `Polling ${runIds.length} run(s)…`\n const finals = await Promise.all(\n runIds.map((id) =>\n poll<RunStatus>({\n fetch: () => ctx.client.json({ method: \"GET\", path: `/studio/runs/${id}` }),\n done: (s) => [\"succeeded\", \"failed\", \"cancelled\"].includes(s.status),\n })\n )\n )\n spinner?.succeed(\"Run(s) complete.\")\n renderResult(ctx, { runs: finals })\n } catch (e) {\n spinner?.fail(\"Campaign failed.\")\n throw e\n }\n })\n}\n","/**\n * `wm jobs list|get` — inspect async generation jobs and runs.\n */\nimport type { Command } from \"commander\"\nimport { makeCtx, requireAuth, renderResult } from \"./_shared.js\"\n\nexport function registerJobs(program: Command): void {\n const jobs = program.command(\"jobs\").description(\"List and inspect generation jobs.\")\n\n jobs\n .command(\"list\")\n .description(\"List recent jobs (default: 20 most recent).\")\n .option(\"-l, --limit <n>\", \"Number of jobs\", \"20\")\n .option(\"-s, --status <s>\", \"Filter by status (running|succeeded|failed|cancelled)\")\n .action(async (opts: { limit?: string; status?: string }) => {\n const ctx = makeCtx(program)\n requireAuth(ctx)\n const data = await ctx.client.json({\n method: \"GET\",\n path: \"/studio/jobs\",\n query: { limit: opts.limit, status: opts.status },\n })\n renderResult(ctx, data)\n })\n\n jobs\n .command(\"get <jobId>\")\n .description(\"Fetch a single job by id.\")\n .action(async (jobId: string) => {\n const ctx = makeCtx(program)\n requireAuth(ctx)\n const data = await ctx.client.json({ method: \"GET\", path: `/studio/jobs/${jobId}` })\n renderResult(ctx, data)\n })\n}\n","// Entrypoint. tsup prepends the `#!/usr/bin/env node` shebang in the build banner.\nimport { run } from \"./cli.js\"\nimport { WmCliError, ExitCode } from \"./errors.js\"\nimport { logger } from \"./logger.js\"\n\nrun(process.argv).catch((err: unknown) => {\n if (err instanceof WmCliError) {\n logger.error(err.message, err.details)\n process.exit(err.exitCode)\n }\n const message = err instanceof Error ? err.message : String(err)\n logger.error(`unexpected error: ${message}`)\n process.exit(ExitCode.UNEXPECTED)\n})\n"],"mappings":"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","names":["kleur","z","z","kleur","dirname","url","ora","ora","ora","ora"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wmstudio-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Command-line client for the WM Studio creative platform. Generates images, videos, brand campaigns, and 3D assets from your terminal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -44,10 +44,9 @@
|
|
|
44
44
|
"fal-ai",
|
|
45
45
|
"creative-tools"
|
|
46
46
|
],
|
|
47
|
-
"homepage": "https://wmstudio.io",
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"url": "https://github.com/PrincipeRosso/wm-cli.git"
|
|
47
|
+
"homepage": "https://wmstudio.io/docs/cli",
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://wmstudio.io/support"
|
|
51
50
|
},
|
|
52
51
|
"license": "MIT",
|
|
53
52
|
"publishConfig": {
|