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