@warlok-net/cli 0.1.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.
@@ -0,0 +1,781 @@
1
+ // src/index.ts
2
+ import { mkdir, readFile, writeFile } from "fs/promises";
3
+ import os from "os";
4
+ import path from "path";
5
+ import { stdin as input, stdout as output } from "process";
6
+ import { createInterface } from "readline/promises";
7
+ import {
8
+ WarlokClient,
9
+ WarlokError
10
+ } from "@warlok-net/sdk";
11
+ var SHORT_FLAG_MAP = {
12
+ p: "prompt",
13
+ d: "description",
14
+ i: "image",
15
+ e: "entity",
16
+ t: "entity",
17
+ n: "name",
18
+ o: "output",
19
+ k: "api-key",
20
+ u: "base-url"
21
+ };
22
+ var DEFAULT_API_URL = "https://api.warlok.net";
23
+ var DEFAULT_CONFIG_FILENAME = "config.json";
24
+ var GENERATION_STATUSES = /* @__PURE__ */ new Set([
25
+ "pending",
26
+ "preprocessing",
27
+ "generating",
28
+ "rigging",
29
+ "complete",
30
+ "failed"
31
+ ]);
32
+ var ENTITY_ALIASES = {
33
+ noun: "noun",
34
+ person: "person",
35
+ place: "place",
36
+ thing: "thing",
37
+ character: "person",
38
+ creature: "person",
39
+ environment: "place",
40
+ prop: "thing",
41
+ weapon: "thing",
42
+ vehicle: "thing",
43
+ material: "thing"
44
+ };
45
+ function parseArgs(args) {
46
+ const positional = [];
47
+ const options = {};
48
+ for (let i = 0; i < args.length; i += 1) {
49
+ const token = args[i];
50
+ if (token === "--") {
51
+ positional.push(...args.slice(i + 1));
52
+ break;
53
+ }
54
+ if (token.startsWith("--no-")) {
55
+ options[token.slice(5)] = false;
56
+ continue;
57
+ }
58
+ if (token.startsWith("--")) {
59
+ const [rawKey, inlineValue] = token.slice(2).split("=", 2);
60
+ if (inlineValue !== void 0) {
61
+ options[rawKey] = inlineValue;
62
+ continue;
63
+ }
64
+ const next = args[i + 1];
65
+ if (next && !next.startsWith("-")) {
66
+ options[rawKey] = next;
67
+ i += 1;
68
+ } else {
69
+ options[rawKey] = true;
70
+ }
71
+ continue;
72
+ }
73
+ if (token.startsWith("-") && token.length > 1) {
74
+ const raw = token.slice(1);
75
+ if (raw.length > 1) {
76
+ const alias2 = SHORT_FLAG_MAP[raw[0]];
77
+ if (alias2) {
78
+ options[alias2] = raw.slice(1);
79
+ continue;
80
+ }
81
+ }
82
+ const alias = SHORT_FLAG_MAP[raw];
83
+ if (!alias) {
84
+ positional.push(token);
85
+ continue;
86
+ }
87
+ const next = args[i + 1];
88
+ if (next && !next.startsWith("-")) {
89
+ options[alias] = next;
90
+ i += 1;
91
+ } else {
92
+ options[alias] = true;
93
+ }
94
+ continue;
95
+ }
96
+ positional.push(token);
97
+ }
98
+ return { positional, options };
99
+ }
100
+ function stringOption(options, key) {
101
+ const value = options[key];
102
+ if (typeof value === "string" && value.trim()) {
103
+ return value.trim();
104
+ }
105
+ return void 0;
106
+ }
107
+ function numberOption(options, key) {
108
+ const raw = stringOption(options, key);
109
+ if (!raw) {
110
+ return void 0;
111
+ }
112
+ const parsed = Number(raw);
113
+ return Number.isFinite(parsed) ? parsed : void 0;
114
+ }
115
+ function booleanOption(options, key, defaultValue) {
116
+ const value = options[key];
117
+ if (typeof value === "boolean") {
118
+ return value;
119
+ }
120
+ if (typeof value === "string") {
121
+ const normalized = value.toLowerCase();
122
+ if (normalized === "false" || normalized === "0" || normalized === "no") {
123
+ return false;
124
+ }
125
+ if (normalized === "true" || normalized === "1" || normalized === "yes") {
126
+ return true;
127
+ }
128
+ }
129
+ return defaultValue;
130
+ }
131
+ function isUrl(value) {
132
+ try {
133
+ const parsed = new URL(value);
134
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
135
+ } catch {
136
+ return false;
137
+ }
138
+ }
139
+ function normalizeEntityType(raw) {
140
+ if (!raw) {
141
+ return "thing";
142
+ }
143
+ const normalized = raw.toLowerCase();
144
+ const mapped = ENTITY_ALIASES[normalized];
145
+ if (!mapped) {
146
+ throw new Error(
147
+ `Unsupported entity type "${raw}". Use noun|person|place|thing.`
148
+ );
149
+ }
150
+ return mapped;
151
+ }
152
+ function resolveConfigPath(options) {
153
+ return stringOption(options || {}, "config") || process.env.WARLOK_CONFIG_PATH || path.join(os.homedir(), ".warlok", DEFAULT_CONFIG_FILENAME);
154
+ }
155
+ async function readCliConfig(options) {
156
+ const configPath = resolveConfigPath(options);
157
+ try {
158
+ const raw = await readFile(configPath, "utf8");
159
+ const parsed = JSON.parse(raw);
160
+ if (!parsed || typeof parsed !== "object") {
161
+ return {};
162
+ }
163
+ return parsed;
164
+ } catch {
165
+ return {};
166
+ }
167
+ }
168
+ async function writeCliConfig(nextConfig, options) {
169
+ const configPath = resolveConfigPath(options);
170
+ const dir = path.dirname(configPath);
171
+ await mkdir(dir, { recursive: true });
172
+ await writeFile(configPath, `${JSON.stringify(nextConfig, null, 2)}
173
+ `, "utf8");
174
+ return configPath;
175
+ }
176
+ function resolveBaseUrl(options, config) {
177
+ return stringOption(options, "base-url") || process.env.WARLOK_API_URL || config?.apiUrl || DEFAULT_API_URL;
178
+ }
179
+ function isLocalBaseUrl(baseUrl) {
180
+ try {
181
+ const parsed = new URL(baseUrl);
182
+ return parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname.endsWith(".local");
183
+ } catch {
184
+ return false;
185
+ }
186
+ }
187
+ function resolveApiKey(options, baseUrl, config) {
188
+ const provided = stringOption(options, "api-key") || process.env.WARLOK_API_KEY || config?.apiKey;
189
+ if (provided) {
190
+ return provided;
191
+ }
192
+ if (isLocalBaseUrl(baseUrl)) {
193
+ return "wk_dev_local";
194
+ }
195
+ throw new Error(
196
+ "Missing API key. Set WARLOK_API_KEY or pass --api-key <key>."
197
+ );
198
+ }
199
+ function createClient(options, config) {
200
+ const baseUrl = resolveBaseUrl(options, config);
201
+ const apiKey = resolveApiKey(options, baseUrl, config);
202
+ return new WarlokClient({ apiKey, baseUrl });
203
+ }
204
+ function shouldPrintJson(options) {
205
+ return booleanOption(options, "json", false);
206
+ }
207
+ function print(value, asJson) {
208
+ if (asJson) {
209
+ process.stdout.write(`${JSON.stringify(value, null, 2)}
210
+ `);
211
+ return;
212
+ }
213
+ if (typeof value === "string") {
214
+ process.stdout.write(`${value}
215
+ `);
216
+ return;
217
+ }
218
+ process.stdout.write(`${JSON.stringify(value, null, 2)}
219
+ `);
220
+ }
221
+ async function loadImageInput(imageInput) {
222
+ if (!imageInput) {
223
+ return {};
224
+ }
225
+ if (isUrl(imageInput)) {
226
+ return { imageUrl: imageInput };
227
+ }
228
+ const absolutePath = path.resolve(process.cwd(), imageInput);
229
+ const data = await readFile(absolutePath);
230
+ return { imageBase64: data.toString("base64") };
231
+ }
232
+ function defaultOutputPath(id, format) {
233
+ return path.resolve(
234
+ process.cwd(),
235
+ format === "rigged" ? `${id}-rigged.glb` : `${id}.glb`
236
+ );
237
+ }
238
+ async function writeBlobToFile(blob, outputPath) {
239
+ const arrayBuffer = await blob.arrayBuffer();
240
+ const buffer = Buffer.from(arrayBuffer);
241
+ await writeFile(outputPath, buffer);
242
+ }
243
+ function formatGenerationSummary(generation) {
244
+ return {
245
+ id: generation.id,
246
+ name: generation.name,
247
+ entityType: generation.entityType,
248
+ status: generation.status,
249
+ progress: generation.progress,
250
+ modelUrl: generation.outputs.modelUrl,
251
+ riggedModelUrl: generation.outputs.riggedModelUrl,
252
+ error: generation.error,
253
+ createdAt: generation.createdAt,
254
+ updatedAt: generation.updatedAt
255
+ };
256
+ }
257
+ async function runGenerate(args) {
258
+ const parsed = parseArgs(args);
259
+ const options = parsed.options;
260
+ const positional = [...parsed.positional];
261
+ const isCharacterSubcommand = positional[0]?.toLowerCase() === "character";
262
+ const descriptionPositional = isCharacterSubcommand ? positional.slice(1).join(" ").trim() : "";
263
+ const prompt = stringOption(options, "prompt") || stringOption(options, "description") || (descriptionPositional || void 0);
264
+ const image = stringOption(options, "image");
265
+ if (!prompt && !image) {
266
+ if (isCharacterSubcommand) {
267
+ throw new Error(
268
+ "Character generation requires a description and/or --image."
269
+ );
270
+ }
271
+ throw new Error("Generate requires --prompt/--description and/or --image.");
272
+ }
273
+ const requestedEntity = stringOption(options, "entity") || stringOption(options, "type");
274
+ const entityType = isCharacterSubcommand ? "person" : normalizeEntityType(requestedEntity);
275
+ if (isCharacterSubcommand && requestedEntity) {
276
+ const normalized = normalizeEntityType(requestedEntity);
277
+ if (normalized !== "person") {
278
+ throw new Error(
279
+ "Character subcommand always generates entity type person. Remove --entity/--type override."
280
+ );
281
+ }
282
+ }
283
+ const config = await readCliConfig(options);
284
+ const client = createClient(options, config);
285
+ const { imageUrl, imageBase64 } = await loadImageInput(image);
286
+ const generation = await client.generate({
287
+ name: stringOption(options, "name"),
288
+ entityType,
289
+ prompt,
290
+ imageUrl,
291
+ imageBase64,
292
+ options: {
293
+ rigForMixamo: booleanOption(options, "rig", entityType === "person"),
294
+ includeTpose: booleanOption(options, "tpose", entityType === "person"),
295
+ optimize: booleanOption(options, "optimize", true)
296
+ }
297
+ });
298
+ const wait = booleanOption(options, "wait", false);
299
+ const asJson = shouldPrintJson(options);
300
+ if (!wait) {
301
+ print(
302
+ {
303
+ ...formatGenerationSummary(generation),
304
+ message: "Generation queued. Use `warlok status <id>` to check progress, or pass --wait."
305
+ },
306
+ asJson
307
+ );
308
+ return;
309
+ }
310
+ const completed = await client.waitForCompletion(generation.id, {
311
+ pollingInterval: numberOption(options, "interval") || 2e3,
312
+ timeout: numberOption(options, "timeout") || 3e5
313
+ });
314
+ print(formatGenerationSummary(completed), asJson);
315
+ }
316
+ async function runStatus(args) {
317
+ const parsed = parseArgs(args);
318
+ const id = parsed.positional[0];
319
+ if (!id) {
320
+ throw new Error("Status requires a generation id: warlok status <id>");
321
+ }
322
+ const config = await readCliConfig(parsed.options);
323
+ const client = createClient(parsed.options, config);
324
+ const generation = await client.getGeneration(id);
325
+ print(formatGenerationSummary(generation), shouldPrintJson(parsed.options));
326
+ }
327
+ async function runList(args) {
328
+ const parsed = parseArgs(args);
329
+ const statusInput = stringOption(parsed.options, "status");
330
+ const status = statusInput ? statusInput : void 0;
331
+ if (status && !GENERATION_STATUSES.has(status)) {
332
+ throw new Error(
333
+ `Unsupported status "${statusInput}". Use pending|preprocessing|generating|rigging|complete|failed.`
334
+ );
335
+ }
336
+ const config = await readCliConfig(parsed.options);
337
+ const client = createClient(parsed.options, config);
338
+ const generations = await client.listGenerations({
339
+ limit: numberOption(parsed.options, "limit"),
340
+ status,
341
+ entityType: normalizeEntityType(stringOption(parsed.options, "entity"))
342
+ });
343
+ const rows = generations.map(formatGenerationSummary);
344
+ print({ total: rows.length, items: rows }, true);
345
+ }
346
+ async function runDownload(args) {
347
+ const parsed = parseArgs(args);
348
+ const id = parsed.positional[0];
349
+ if (!id) {
350
+ throw new Error("Download requires a generation id: warlok download <id>");
351
+ }
352
+ const format = booleanOption(
353
+ parsed.options,
354
+ "rigged",
355
+ false
356
+ ) ? "rigged" : booleanOption(parsed.options, "model", false) ? "model" : void 0;
357
+ const outputPath = stringOption(parsed.options, "output") || defaultOutputPath(id, format);
358
+ const config = await readCliConfig(parsed.options);
359
+ const client = createClient(parsed.options, config);
360
+ const blob = await client.downloadGeneration(id, format);
361
+ await writeBlobToFile(blob, outputPath);
362
+ print(
363
+ {
364
+ id,
365
+ format: format || "best_available",
366
+ output: outputPath
367
+ },
368
+ true
369
+ );
370
+ }
371
+ async function runTools(args) {
372
+ const parsed = parseArgs(args);
373
+ const config = await readCliConfig(parsed.options);
374
+ const baseUrl = resolveBaseUrl(parsed.options, config);
375
+ const apiKey = resolveApiKey(parsed.options, baseUrl, config);
376
+ const response = await fetch(`${baseUrl}/api/v1/mcp/tools`, {
377
+ headers: {
378
+ Authorization: `Bearer ${apiKey}`
379
+ }
380
+ });
381
+ const payload = await response.json();
382
+ if (!response.ok) {
383
+ throw new Error(
384
+ payload?.error?.message || `Failed to fetch tools (${response.status})`
385
+ );
386
+ }
387
+ print(payload?.data || payload, true);
388
+ }
389
+ async function runAgent(args) {
390
+ const [subcommand, ...rest] = args;
391
+ if (subcommand !== "run") {
392
+ throw new Error("Agent command requires `run`: warlok agent run <goal>");
393
+ }
394
+ const parsed = parseArgs(rest);
395
+ const goal = parsed.positional.join(" ").trim();
396
+ if (!goal) {
397
+ throw new Error("Agent goal is required: warlok agent run <goal>");
398
+ }
399
+ const config = await readCliConfig(parsed.options);
400
+ const baseUrl = resolveBaseUrl(parsed.options, config);
401
+ const apiKey = resolveApiKey(parsed.options, baseUrl, config);
402
+ const toolsResponse = await fetch(`${baseUrl}/api/v1/mcp/tools`, {
403
+ headers: {
404
+ Authorization: `Bearer ${apiKey}`
405
+ }
406
+ });
407
+ const toolsPayload = await toolsResponse.json();
408
+ const tools = toolsPayload?.data?.tools || [];
409
+ print(
410
+ {
411
+ goal,
412
+ message: "Use the listed MCP tools from your agent runtime to execute this goal.",
413
+ tools: tools.map((tool) => ({
414
+ name: tool.name,
415
+ description: tool.description
416
+ }))
417
+ },
418
+ true
419
+ );
420
+ }
421
+ function redactApiKey(key) {
422
+ if (!key) {
423
+ return void 0;
424
+ }
425
+ if (key.length <= 8) {
426
+ return "****";
427
+ }
428
+ return `${key.slice(0, 4)}...${key.slice(-4)}`;
429
+ }
430
+ async function promptForApiKey() {
431
+ const rl = createInterface({ input, output });
432
+ try {
433
+ const entered = (await rl.question("Enter WARLOK API key: ")).trim();
434
+ if (!entered) {
435
+ throw new Error("API key is required.");
436
+ }
437
+ return entered;
438
+ } finally {
439
+ rl.close();
440
+ }
441
+ }
442
+ async function verifyCredentials(apiKey, baseUrl) {
443
+ try {
444
+ const response = await fetch(`${baseUrl}/api/v1/generations`, {
445
+ headers: {
446
+ Authorization: `Bearer ${apiKey}`
447
+ }
448
+ });
449
+ if (response.ok) {
450
+ return { ok: true };
451
+ }
452
+ let message = `HTTP ${response.status}`;
453
+ try {
454
+ const payload = await response.json();
455
+ message = payload?.error?.message || payload?.error?.code || message;
456
+ } catch {
457
+ }
458
+ return { ok: false, reason: message };
459
+ } catch (error) {
460
+ return {
461
+ ok: false,
462
+ reason: error instanceof Error ? error.message : "request failed"
463
+ };
464
+ }
465
+ }
466
+ async function runDoctor(args) {
467
+ const parsed = parseArgs(args);
468
+ const asJson = shouldPrintJson(parsed.options);
469
+ const config = await readCliConfig(parsed.options);
470
+ const baseUrl = resolveBaseUrl(parsed.options, config);
471
+ const hasApiKey = Boolean(
472
+ stringOption(parsed.options, "api-key") || process.env.WARLOK_API_KEY || config.apiKey
473
+ );
474
+ const checks = [];
475
+ checks.push({
476
+ name: "base_url",
477
+ ok: isUrl(baseUrl),
478
+ detail: isUrl(baseUrl) ? baseUrl : `invalid URL: ${baseUrl}`
479
+ });
480
+ let healthOk = false;
481
+ try {
482
+ const response = await fetch(`${baseUrl}/api/v1/health`);
483
+ healthOk = response.ok;
484
+ checks.push({
485
+ name: "health",
486
+ ok: response.ok,
487
+ detail: response.ok ? "reachable" : `HTTP ${response.status}`
488
+ });
489
+ } catch (error) {
490
+ checks.push({
491
+ name: "health",
492
+ ok: false,
493
+ detail: error instanceof Error ? error.message : "request failed"
494
+ });
495
+ }
496
+ checks.push({
497
+ name: "api_key",
498
+ ok: hasApiKey,
499
+ detail: hasApiKey ? "configured" : "missing (WARLOK_API_KEY/config/--api-key)"
500
+ });
501
+ if (hasApiKey && healthOk) {
502
+ try {
503
+ const client = createClient(parsed.options, config);
504
+ await client.listGenerations({ limit: 1 });
505
+ checks.push({
506
+ name: "auth",
507
+ ok: true,
508
+ detail: "API key accepted"
509
+ });
510
+ } catch (error) {
511
+ checks.push({
512
+ name: "auth",
513
+ ok: false,
514
+ detail: formatCliError(error)
515
+ });
516
+ }
517
+ }
518
+ if (hasApiKey && healthOk) {
519
+ try {
520
+ const apiKey = resolveApiKey(parsed.options, baseUrl, config);
521
+ const response = await fetch(`${baseUrl}/api/v1/mcp/tools`, {
522
+ headers: {
523
+ Authorization: `Bearer ${apiKey}`
524
+ }
525
+ });
526
+ checks.push({
527
+ name: "mcp_tools",
528
+ ok: response.ok,
529
+ detail: response.ok ? "reachable" : `HTTP ${response.status}`
530
+ });
531
+ } catch (error) {
532
+ checks.push({
533
+ name: "mcp_tools",
534
+ ok: false,
535
+ detail: error instanceof Error ? error.message : "request failed"
536
+ });
537
+ }
538
+ }
539
+ const summary = {
540
+ ok: checks.every((check) => check.ok),
541
+ baseUrl,
542
+ configPath: resolveConfigPath(parsed.options),
543
+ checks
544
+ };
545
+ if (asJson) {
546
+ print(summary, true);
547
+ return;
548
+ }
549
+ process.stdout.write("Warlok doctor\n\n");
550
+ for (const check of checks) {
551
+ process.stdout.write(
552
+ `${check.ok ? "OK" : "FAIL"} ${check.name}: ${check.detail}
553
+ `
554
+ );
555
+ }
556
+ process.stdout.write(`
557
+ Overall: ${summary.ok ? "healthy" : "issues found"}
558
+ `);
559
+ }
560
+ async function runAuth(args) {
561
+ const [subcommand, ...rest] = args;
562
+ const parsed = parseArgs(rest);
563
+ const asJson = shouldPrintJson(parsed.options);
564
+ const config = await readCliConfig(parsed.options);
565
+ const baseUrl = resolveBaseUrl(parsed.options, config);
566
+ switch (subcommand) {
567
+ case "login": {
568
+ const apiKey = stringOption(parsed.options, "api-key") || process.env.WARLOK_API_KEY || await promptForApiKey();
569
+ const nextConfig = {
570
+ ...config,
571
+ apiUrl: baseUrl,
572
+ apiKey,
573
+ profile: config.profile || "default"
574
+ };
575
+ const verify = await verifyCredentials(apiKey, baseUrl);
576
+ if (!verify.ok) {
577
+ throw new Error(`Auth verification failed: ${verify.reason || "unknown"}`);
578
+ }
579
+ const savedPath = await writeCliConfig(nextConfig, parsed.options);
580
+ print(
581
+ {
582
+ message: "Login successful",
583
+ apiUrl: baseUrl,
584
+ apiKey: redactApiKey(apiKey),
585
+ configPath: savedPath
586
+ },
587
+ asJson
588
+ );
589
+ return;
590
+ }
591
+ case "whoami": {
592
+ const effectiveApiKey = stringOption(parsed.options, "api-key") || process.env.WARLOK_API_KEY || config.apiKey;
593
+ const verify = effectiveApiKey ? await verifyCredentials(effectiveApiKey, baseUrl) : { ok: false, reason: "no API key configured" };
594
+ print(
595
+ {
596
+ authenticated: verify.ok,
597
+ reason: verify.ok ? void 0 : verify.reason,
598
+ apiUrl: baseUrl,
599
+ apiKey: redactApiKey(effectiveApiKey),
600
+ configPath: resolveConfigPath(parsed.options),
601
+ source: {
602
+ apiKey: stringOption(parsed.options, "api-key") ? "flag" : process.env.WARLOK_API_KEY ? "env" : config.apiKey ? "config" : "none"
603
+ }
604
+ },
605
+ asJson
606
+ );
607
+ return;
608
+ }
609
+ case "logout": {
610
+ const keepBaseUrl = booleanOption(parsed.options, "keep-base-url", true);
611
+ const nextConfig = {
612
+ ...config,
613
+ apiKey: void 0,
614
+ apiUrl: keepBaseUrl ? config.apiUrl : void 0
615
+ };
616
+ const savedPath = await writeCliConfig(nextConfig, parsed.options);
617
+ print(
618
+ {
619
+ message: "Logged out",
620
+ configPath: savedPath,
621
+ keptBaseUrl: keepBaseUrl
622
+ },
623
+ asJson
624
+ );
625
+ return;
626
+ }
627
+ default:
628
+ throw new Error("Auth command requires login|whoami|logout");
629
+ }
630
+ }
631
+ async function runMcp(args) {
632
+ const [subcommand, ...rest] = args;
633
+ const parsed = parseArgs(rest);
634
+ const config = await readCliConfig(parsed.options);
635
+ const baseUrl = resolveBaseUrl(parsed.options, config);
636
+ const apiKey = resolveApiKey(parsed.options, baseUrl, config);
637
+ if (subcommand !== "print-config") {
638
+ throw new Error("MCP command requires `print-config`.");
639
+ }
640
+ const json = {
641
+ mcpServers: {
642
+ warlok: {
643
+ command: "pnpm",
644
+ args: ["dlx", "@warlok-net/mcp-server@latest"],
645
+ env: {
646
+ WARLOK_API_URL: baseUrl,
647
+ WARLOK_API_KEY: apiKey
648
+ }
649
+ }
650
+ }
651
+ };
652
+ const format = stringOption(parsed.options, "format") || "json";
653
+ if (format === "json") {
654
+ print(json, true);
655
+ return;
656
+ }
657
+ if (format === "env") {
658
+ process.stdout.write(`WARLOK_API_URL=${baseUrl}
659
+ `);
660
+ process.stdout.write(`WARLOK_API_KEY=${apiKey}
661
+ `);
662
+ process.stdout.write("pnpm dlx @warlok-net/mcp-server@latest\n");
663
+ return;
664
+ }
665
+ throw new Error("Unsupported MCP format. Use --format json|env.");
666
+ }
667
+ function mainHelpText() {
668
+ return `
669
+ Warlok CLI
670
+
671
+ Usage:
672
+ warlok <command> [options]
673
+
674
+ Commands:
675
+ generate Start a generation from prompt/image
676
+ status <id> Get generation status
677
+ list List generations
678
+ download <id> Download generated model
679
+ auth Login/logout and inspect CLI auth state
680
+ doctor Validate local CLI/API configuration
681
+ mcp MCP helper commands
682
+ tools List MCP-compatible tools from API
683
+ agent run Print agent execution context for a goal
684
+
685
+ Global options:
686
+ --api-key <key> API key (or set WARLOK_API_KEY)
687
+ --base-url <url> API base URL (or set WARLOK_API_URL)
688
+ --config <path> Config path (or set WARLOK_CONFIG_PATH)
689
+ --json JSON output where supported
690
+
691
+ Generate options:
692
+ generate character "<description>" [--image <path-or-url>] [--wait]
693
+ --prompt, -p <text>
694
+ --description, -d <text> Alias for --prompt
695
+ --image, -i <path-or-url>
696
+ --entity, -e <noun|person|place|thing> (aliases supported)
697
+ --name, -n <name>
698
+ --wait Poll until terminal status
699
+ --interval <ms> Poll interval when --wait (default 2000)
700
+ --timeout <ms> Timeout when --wait (default 300000)
701
+ --rig / --no-rig
702
+ --tpose / --no-tpose
703
+ --optimize / --no-optimize
704
+
705
+ Download options:
706
+ --rigged Prefer rigged model
707
+ --model Prefer base model
708
+ --output, -o <path> Save path (default: ./<id>.glb)
709
+
710
+ Auth:
711
+ auth login [--api-key <key>] [--base-url <url>]
712
+ auth whoami
713
+ auth logout [--keep-base-url=true]
714
+
715
+ Doctor:
716
+ doctor [--json]
717
+
718
+ MCP:
719
+ mcp print-config [--format json|env]
720
+ `.trimStart();
721
+ }
722
+ async function run(argv) {
723
+ const [command, ...rest] = argv;
724
+ if (!command || command === "help" || command === "--help" || command === "-h") {
725
+ process.stdout.write(mainHelpText());
726
+ process.stdout.write("\n");
727
+ return;
728
+ }
729
+ switch (command) {
730
+ case "generate":
731
+ await runGenerate(rest);
732
+ return;
733
+ case "status":
734
+ await runStatus(rest);
735
+ return;
736
+ case "list":
737
+ await runList(rest);
738
+ return;
739
+ case "download":
740
+ await runDownload(rest);
741
+ return;
742
+ case "auth":
743
+ await runAuth(rest);
744
+ return;
745
+ case "doctor":
746
+ await runDoctor(rest);
747
+ return;
748
+ case "mcp":
749
+ await runMcp(rest);
750
+ return;
751
+ case "tools":
752
+ await runTools(rest);
753
+ return;
754
+ case "agent":
755
+ await runAgent(rest);
756
+ return;
757
+ default:
758
+ throw new Error(`Unknown command: ${command}`);
759
+ }
760
+ }
761
+ function formatCliError(error) {
762
+ if (error instanceof WarlokError) {
763
+ return `${error.message} [${error.code}]`;
764
+ }
765
+ if (error instanceof Error) {
766
+ return error.message;
767
+ }
768
+ return "Unknown error";
769
+ }
770
+
771
+ export {
772
+ runGenerate,
773
+ runStatus,
774
+ runList,
775
+ runDownload,
776
+ runTools,
777
+ runAgent,
778
+ mainHelpText,
779
+ run,
780
+ formatCliError
781
+ };