@theatrical/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/index.js ADDED
@@ -0,0 +1,1303 @@
1
+ // src/index.ts
2
+ import { Command as Command4 } from "commander";
3
+
4
+ // src/utils/output.ts
5
+ import chalk from "chalk";
6
+ function success(message) {
7
+ return chalk.green(`\u2713 ${message}`);
8
+ }
9
+ function error(message) {
10
+ return chalk.red(`\u2717 ${message}`);
11
+ }
12
+ function info(message) {
13
+ return chalk.blue(`\u2139 ${message}`);
14
+ }
15
+ function dim(message) {
16
+ return chalk.dim(message);
17
+ }
18
+ function highlight(message) {
19
+ return chalk.cyan(message);
20
+ }
21
+ function printBanner(version) {
22
+ const width = 32;
23
+ const top = `\u250C${"\u2500".repeat(width)}\u2510`;
24
+ const bottom = `\u2514${"\u2500".repeat(width)}\u2518`;
25
+ const pad = (text, len) => text + " ".repeat(Math.max(0, len - stripAnsi(text).length));
26
+ console.log(chalk.dim(top));
27
+ console.log(
28
+ chalk.dim("\u2502") + " " + pad(chalk.bold(`\u{1F3AC} Theatrical CLI v${version}`), width - 2) + chalk.dim("\u2502")
29
+ );
30
+ console.log(
31
+ chalk.dim("\u2502") + " " + pad(chalk.dim("Cinema Platform Dev Tools"), width - 2) + chalk.dim("\u2502")
32
+ );
33
+ console.log(chalk.dim(bottom));
34
+ }
35
+ function keyValue(key, value) {
36
+ return `${chalk.bold(key)}: ${chalk.cyan(value)}`;
37
+ }
38
+ function heading(text) {
39
+ return `
40
+ ${chalk.bold.white(text)}
41
+ ${chalk.dim("\u2500".repeat(text.length))}`;
42
+ }
43
+ function stripAnsi(str) {
44
+ return str.replace(
45
+ // eslint-disable-next-line no-control-regex
46
+ /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
47
+ ""
48
+ );
49
+ }
50
+
51
+ // src/utils/config.ts
52
+ import { cosmiconfig } from "cosmiconfig";
53
+ import * as path from "path";
54
+ import * as fs from "fs";
55
+ import * as os from "os";
56
+ var DEFAULTS = {
57
+ apiUrl: "https://api.vista.co/ocapi/v1",
58
+ outputFormat: "pretty",
59
+ verbose: false,
60
+ color: true
61
+ };
62
+ var ENV_PREFIX = "THEATRICAL_";
63
+ var CONFIG_DIR = ".config/theatrical";
64
+ var CONFIG_FILE = "config.json";
65
+ async function resolveConfig(overrides = {}) {
66
+ const fileConfig = await loadFileConfig();
67
+ const envConfig = loadEnvConfig();
68
+ return {
69
+ ...DEFAULTS,
70
+ ...fileConfig,
71
+ ...envConfig,
72
+ ...stripUndefined(overrides)
73
+ };
74
+ }
75
+ async function loadFileConfig() {
76
+ const explorer = cosmiconfig("theatrical", {
77
+ searchPlaces: [
78
+ ".theatricalrc",
79
+ ".theatricalrc.json",
80
+ ".theatricalrc.yaml",
81
+ ".theatricalrc.yml",
82
+ "theatrical.config.js",
83
+ "theatrical.config.cjs",
84
+ "theatrical.config.mjs",
85
+ "package.json"
86
+ ]
87
+ });
88
+ try {
89
+ const result = await explorer.search();
90
+ if (result && !result.isEmpty) {
91
+ return result.config;
92
+ }
93
+ } catch {
94
+ }
95
+ return loadUserConfig();
96
+ }
97
+ function loadUserConfig() {
98
+ const configPath = path.join(os.homedir(), CONFIG_DIR, CONFIG_FILE);
99
+ try {
100
+ if (fs.existsSync(configPath)) {
101
+ const raw = fs.readFileSync(configPath, "utf-8");
102
+ return JSON.parse(raw);
103
+ }
104
+ } catch {
105
+ }
106
+ return {};
107
+ }
108
+ function loadEnvConfig() {
109
+ const config = {};
110
+ const apiUrl = process.env[`${ENV_PREFIX}API_URL`];
111
+ if (apiUrl) config.apiUrl = apiUrl;
112
+ const apiKey = process.env[`${ENV_PREFIX}API_KEY`];
113
+ if (apiKey) config.apiKey = apiKey;
114
+ const siteId = process.env[`${ENV_PREFIX}DEFAULT_SITE_ID`];
115
+ if (siteId) config.defaultSiteId = siteId;
116
+ const format = process.env[`${ENV_PREFIX}OUTPUT_FORMAT`];
117
+ if (format === "json" || format === "table" || format === "pretty") {
118
+ config.outputFormat = format;
119
+ }
120
+ const verbose = process.env[`${ENV_PREFIX}VERBOSE`];
121
+ if (verbose === "1" || verbose === "true") config.verbose = true;
122
+ const color = process.env[`${ENV_PREFIX}NO_COLOR`];
123
+ if (color === "1" || color === "true") config.color = false;
124
+ return config;
125
+ }
126
+ function stripUndefined(obj) {
127
+ const result = {};
128
+ for (const [key, value] of Object.entries(obj)) {
129
+ if (value !== void 0) {
130
+ result[key] = value;
131
+ }
132
+ }
133
+ return result;
134
+ }
135
+
136
+ // src/commands/init.ts
137
+ import * as fs2 from "fs";
138
+ import * as path2 from "path";
139
+ import { Command } from "commander";
140
+ function createInitCommand() {
141
+ const cmd = new Command("init");
142
+ cmd.argument("[project-name]", "Name of the project to create").description("Scaffold a new cinema platform project").option("-t, --template <template>", "Project template (default, fullstack, worker)", "default").option("--api-key <key>", "Vista API key to include in .env").option("--no-install", "Skip automatic dependency installation").option("-d, --directory <dir>", "Target directory (defaults to project name)").action(async (projectName, opts) => {
143
+ const resolvedConfig = cmd.parent?.opts().resolvedConfig;
144
+ const options = resolveInitOptions(projectName, opts, resolvedConfig);
145
+ await executeInit(options);
146
+ });
147
+ return cmd;
148
+ }
149
+ function resolveInitOptions(projectName, flags, config) {
150
+ const name = projectName ?? "my-cinema-app";
151
+ const template = validateTemplate(flags.template);
152
+ return {
153
+ projectName: name,
154
+ template,
155
+ apiKey: flags.apiKey ?? config?.apiKey,
156
+ skipInstall: flags.install === false,
157
+ directory: flags.directory ?? name
158
+ };
159
+ }
160
+ function validateTemplate(template) {
161
+ const valid = ["default", "fullstack", "worker"];
162
+ if (valid.includes(template)) {
163
+ return template;
164
+ }
165
+ console.warn(error(`Unknown template "${template}". Using "default".`));
166
+ return "default";
167
+ }
168
+ async function executeInit(options) {
169
+ const targetDir = path2.resolve(process.cwd(), options.directory);
170
+ console.log(heading("Theatrical \u2014 Project Scaffolding"));
171
+ console.log();
172
+ console.log(keyValue("Project", options.projectName));
173
+ console.log(keyValue("Template", options.template));
174
+ console.log(keyValue("Directory", targetDir));
175
+ console.log();
176
+ if (fs2.existsSync(targetDir)) {
177
+ const entries = fs2.readdirSync(targetDir);
178
+ if (entries.length > 0) {
179
+ console.log(error(`Directory "${options.directory}" already exists and is not empty.`));
180
+ console.log(dim(" Use a different project name or remove the existing directory."));
181
+ process.exit(1);
182
+ }
183
+ }
184
+ fs2.mkdirSync(targetDir, { recursive: true });
185
+ fs2.mkdirSync(path2.join(targetDir, "src"), { recursive: true });
186
+ const template = getTemplate(options);
187
+ let filesWritten = 0;
188
+ for (const file of template.files) {
189
+ const filePath = path2.join(targetDir, file.path);
190
+ const dir = path2.dirname(filePath);
191
+ fs2.mkdirSync(dir, { recursive: true });
192
+ fs2.writeFileSync(filePath, file.content, "utf-8");
193
+ console.log(dim(` created ${file.path}`));
194
+ filesWritten++;
195
+ }
196
+ console.log();
197
+ console.log(success(`Scaffolded ${filesWritten} files`));
198
+ if (!options.skipInstall) {
199
+ console.log();
200
+ console.log(info("To install dependencies:"));
201
+ console.log(dim(` cd ${options.directory}`));
202
+ console.log(dim(" npm install"));
203
+ }
204
+ console.log();
205
+ console.log(highlight("Next steps:"));
206
+ console.log(dim(` cd ${options.directory}`));
207
+ if (options.apiKey) {
208
+ console.log(dim(" # API key is configured in .env"));
209
+ } else {
210
+ console.log(dim(" # Add your Vista API key to .env"));
211
+ }
212
+ console.log(dim(" npx theatrical inspect sessions list --site <your-site-id>"));
213
+ console.log();
214
+ }
215
+ function getTemplate(options) {
216
+ const templates = {
217
+ default: () => defaultTemplate(options),
218
+ fullstack: () => fullstackTemplate(options),
219
+ worker: () => workerTemplate(options)
220
+ };
221
+ return templates[options.template]();
222
+ }
223
+ function defaultTemplate(options) {
224
+ return {
225
+ name: "default",
226
+ description: "Minimal TypeScript project with Theatrical SDK",
227
+ files: [
228
+ {
229
+ path: "package.json",
230
+ content: JSON.stringify(
231
+ {
232
+ name: options.projectName,
233
+ version: "0.1.0",
234
+ private: true,
235
+ type: "module",
236
+ scripts: {
237
+ build: "tsc",
238
+ dev: "tsx watch src/index.ts",
239
+ start: "node dist/index.js"
240
+ },
241
+ dependencies: {
242
+ "@theatrical/sdk": "^0.1.0"
243
+ },
244
+ devDependencies: {
245
+ typescript: "^5.4.0",
246
+ tsx: "^4.0.0",
247
+ "@types/node": "^20.0.0"
248
+ }
249
+ },
250
+ null,
251
+ 2
252
+ ) + "\n"
253
+ },
254
+ {
255
+ path: "tsconfig.json",
256
+ content: JSON.stringify(
257
+ {
258
+ compilerOptions: {
259
+ target: "ES2022",
260
+ module: "ESNext",
261
+ moduleResolution: "bundler",
262
+ strict: true,
263
+ esModuleInterop: true,
264
+ outDir: "dist",
265
+ rootDir: "src",
266
+ declaration: true
267
+ },
268
+ include: ["src/**/*"]
269
+ },
270
+ null,
271
+ 2
272
+ ) + "\n"
273
+ },
274
+ {
275
+ path: ".env",
276
+ content: [
277
+ "# Theatrical SDK Configuration",
278
+ `THEATRICAL_API_KEY=${options.apiKey ?? "your-api-key-here"}`,
279
+ "THEATRICAL_API_URL=https://api.vista.co/ocapi/v1",
280
+ ""
281
+ ].join("\n")
282
+ },
283
+ {
284
+ path: ".env.example",
285
+ content: [
286
+ "# Theatrical SDK Configuration",
287
+ "THEATRICAL_API_KEY=your-api-key-here",
288
+ "THEATRICAL_API_URL=https://api.vista.co/ocapi/v1",
289
+ ""
290
+ ].join("\n")
291
+ },
292
+ {
293
+ path: ".gitignore",
294
+ content: ["node_modules", "dist", ".env", "coverage", "*.log", ""].join("\n")
295
+ },
296
+ {
297
+ path: "src/index.ts",
298
+ content: [
299
+ "import { TheatricalClient } from '@theatrical/sdk';",
300
+ "",
301
+ "// Initialize the SDK client",
302
+ "const client = TheatricalClient.create({",
303
+ " apiKey: process.env.THEATRICAL_API_KEY ?? '',",
304
+ " baseUrl: process.env.THEATRICAL_API_URL ?? 'https://api.vista.co/ocapi/v1',",
305
+ "});",
306
+ "",
307
+ "async function main() {",
308
+ " // List films currently showing",
309
+ " const films = await client.films.list({ nowShowing: true });",
310
+ " console.log('Now Showing:', films);",
311
+ "",
312
+ " // Find nearby cinema sites",
313
+ " // const sites = await client.sites.nearby(-41.2924, 174.7787, 10);",
314
+ " // console.log('Nearby Sites:', sites);",
315
+ "}",
316
+ "",
317
+ "main().catch(console.error);",
318
+ ""
319
+ ].join("\n")
320
+ }
321
+ ]
322
+ };
323
+ }
324
+ function fullstackTemplate(options) {
325
+ const base = defaultTemplate(options);
326
+ return {
327
+ name: "fullstack",
328
+ description: "Express API + React frontend with Theatrical SDK",
329
+ files: [
330
+ ...base.files,
331
+ {
332
+ path: "src/server.ts",
333
+ content: [
334
+ "import express from 'express';",
335
+ "import { TheatricalClient } from '@theatrical/sdk';",
336
+ "",
337
+ "const app = express();",
338
+ "const port = process.env.PORT ?? 3000;",
339
+ "",
340
+ "const client = TheatricalClient.create({",
341
+ " apiKey: process.env.THEATRICAL_API_KEY ?? '',",
342
+ "});",
343
+ "",
344
+ "app.get('/api/films', async (_req, res) => {",
345
+ " const films = await client.films.list({ nowShowing: true });",
346
+ " res.json(films);",
347
+ "});",
348
+ "",
349
+ "app.get('/api/sessions/:siteId', async (req, res) => {",
350
+ " const sessions = await client.sessions.list({",
351
+ " siteId: req.params.siteId,",
352
+ " });",
353
+ " res.json(sessions);",
354
+ "});",
355
+ "",
356
+ "app.listen(port, () => {",
357
+ " console.log(`\u{1F3AC} Cinema API running on http://localhost:${port}`);",
358
+ "});",
359
+ ""
360
+ ].join("\n")
361
+ }
362
+ ]
363
+ };
364
+ }
365
+ function workerTemplate(options) {
366
+ const base = defaultTemplate(options);
367
+ return {
368
+ name: "worker",
369
+ description: "Background worker for cinema event processing",
370
+ files: [
371
+ ...base.files,
372
+ {
373
+ path: "src/worker.ts",
374
+ content: [
375
+ "import { TheatricalClient } from '@theatrical/sdk';",
376
+ "",
377
+ "const client = TheatricalClient.create({",
378
+ " apiKey: process.env.THEATRICAL_API_KEY ?? '',",
379
+ "});",
380
+ "",
381
+ "/** Poll interval in milliseconds */",
382
+ "const POLL_INTERVAL = 30_000;",
383
+ "",
384
+ "async function processEvents() {",
385
+ " console.log('\u{1F3AC} Cinema event worker starting...');",
386
+ "",
387
+ " // Example: poll for session changes",
388
+ " setInterval(async () => {",
389
+ " try {",
390
+ " // Check for updated sessions, availability changes, etc.",
391
+ " console.log(`[${new Date().toISOString()}] Polling for updates...`);",
392
+ " } catch (err) {",
393
+ " console.error('Poll error:', err);",
394
+ " }",
395
+ " }, POLL_INTERVAL);",
396
+ "}",
397
+ "",
398
+ "processEvents().catch(console.error);",
399
+ ""
400
+ ].join("\n")
401
+ }
402
+ ]
403
+ };
404
+ }
405
+
406
+ // src/commands/codegen.ts
407
+ import fs3 from "fs";
408
+ import path3 from "path";
409
+ import { Command as Command2 } from "commander";
410
+
411
+ // src/codegen/parser.ts
412
+ var OpenAPIParser = class {
413
+ /**
414
+ * Parse an OpenAPI 3.x specification object.
415
+ * Supports JSON and YAML (if pre-parsed to objects).
416
+ *
417
+ * @param spec - Raw OpenAPI specification object
418
+ * @returns Parsed specification ready for code generation
419
+ * @throws Error if spec is invalid or missing required fields
420
+ */
421
+ parse(spec) {
422
+ if (!spec || typeof spec !== "object") {
423
+ throw new Error("OpenAPI spec must be a valid object");
424
+ }
425
+ if (!spec.info) {
426
+ throw new Error("OpenAPI spec missing required field: info");
427
+ }
428
+ if (!spec.info.title) {
429
+ throw new Error("OpenAPI spec missing required field: info.title");
430
+ }
431
+ if (!spec.info.version) {
432
+ throw new Error("OpenAPI spec missing required field: info.version");
433
+ }
434
+ const paths = this.parsePaths(spec.paths || {});
435
+ const schemas = this.parseSchemas(spec.components?.schemas || {});
436
+ return {
437
+ title: spec.info.title,
438
+ description: spec.info.description,
439
+ version: spec.info.version,
440
+ paths,
441
+ schemas
442
+ };
443
+ }
444
+ /**
445
+ * Parse OpenAPI paths (endpoints).
446
+ */
447
+ parsePaths(pathsObj) {
448
+ return Object.entries(pathsObj).map(([path4, pathItem]) => ({
449
+ path: path4,
450
+ operations: this.parseOperations(pathItem)
451
+ }));
452
+ }
453
+ /**
454
+ * Parse operations (HTTP methods) for a path item.
455
+ */
456
+ parseOperations(pathItem) {
457
+ const methods = ["get", "post", "put", "delete", "patch", "head", "options"];
458
+ const operations = [];
459
+ for (const method of methods) {
460
+ const operation = pathItem[method];
461
+ if (operation && typeof operation === "object") {
462
+ operations.push({
463
+ method: method.toUpperCase(),
464
+ operationId: operation.operationId || `${method}_unknown`,
465
+ summary: operation.summary,
466
+ description: operation.description,
467
+ requestBody: operation.requestBody ? this.parseRequestBody(operation.requestBody) : void 0,
468
+ responses: this.parseResponses(operation.responses || {}),
469
+ parameters: this.parseParameters(operation.parameters || [])
470
+ });
471
+ }
472
+ }
473
+ return operations;
474
+ }
475
+ /**
476
+ * Parse request body definition.
477
+ */
478
+ parseRequestBody(requestBody) {
479
+ if (!requestBody.content) {
480
+ throw new Error("Request body missing content");
481
+ }
482
+ const jsonContent = requestBody.content["application/json"];
483
+ if (!jsonContent) {
484
+ throw new Error("Request body missing application/json content type");
485
+ }
486
+ return this.parseSchemaObject(jsonContent.schema, "RequestBody");
487
+ }
488
+ /**
489
+ * Parse response definitions.
490
+ */
491
+ parseResponses(responsesObj) {
492
+ const responses = {};
493
+ for (const [status, response] of Object.entries(responsesObj)) {
494
+ if (response && typeof response === "object" && response.content) {
495
+ const jsonContent = response.content["application/json"];
496
+ if (jsonContent) {
497
+ responses[status] = this.parseSchemaObject(jsonContent.schema, `Response${status}`);
498
+ }
499
+ }
500
+ }
501
+ return responses;
502
+ }
503
+ /**
504
+ * Parse parameters (query, path, header, cookie).
505
+ */
506
+ parseParameters(parameters) {
507
+ return parameters.map((param) => ({
508
+ name: param.name,
509
+ in: param.in,
510
+ required: param.required === true,
511
+ type: this.schemaToTypeString(param.schema),
512
+ description: param.description
513
+ }));
514
+ }
515
+ /**
516
+ * Parse a schema object (from definitions or inline).
517
+ */
518
+ parseSchemaObject(schema, defaultName) {
519
+ if (!schema) {
520
+ return { name: defaultName, type: "object" };
521
+ }
522
+ if (schema.$ref) {
523
+ const refName = schema.$ref.split("/").pop() || defaultName;
524
+ return {
525
+ name: refName,
526
+ type: "object",
527
+ isReference: true
528
+ };
529
+ }
530
+ if (Array.isArray(schema.enum)) {
531
+ return {
532
+ name: defaultName,
533
+ type: "enum",
534
+ description: schema.description,
535
+ enumValues: schema.enum.map(String)
536
+ };
537
+ }
538
+ if (schema.type === "array") {
539
+ return {
540
+ name: defaultName,
541
+ type: "array",
542
+ description: schema.description,
543
+ items: schema.items ? this.parseSchemaObject(schema.items, `${defaultName}Item`) : void 0
544
+ };
545
+ }
546
+ if (schema.type === "object" || schema.properties) {
547
+ const properties = {};
548
+ const required = schema.required || [];
549
+ if (schema.properties) {
550
+ for (const [propName, propSchema] of Object.entries(schema.properties || {})) {
551
+ properties[propName] = {
552
+ name: propName,
553
+ required: required.includes(propName),
554
+ schema: this.parseSchemaObject(propSchema, propName),
555
+ description: propSchema?.description
556
+ };
557
+ }
558
+ }
559
+ return {
560
+ name: defaultName,
561
+ type: "object",
562
+ description: schema.description,
563
+ properties,
564
+ required
565
+ };
566
+ }
567
+ return {
568
+ name: defaultName,
569
+ type: schema.type || "object",
570
+ description: schema.description
571
+ };
572
+ }
573
+ /**
574
+ * Parse schema definitions (components/schemas).
575
+ */
576
+ parseSchemas(schemasObj) {
577
+ return Object.entries(schemasObj).map(([name, schema]) => {
578
+ const parsed = this.parseSchemaObject(schema, name);
579
+ parsed.name = name;
580
+ return parsed;
581
+ });
582
+ }
583
+ /**
584
+ * Convert a schema to a TypeScript type string (for parameters).
585
+ */
586
+ schemaToTypeString(schema) {
587
+ if (!schema) return "unknown";
588
+ if (schema.type === "string") {
589
+ if (Array.isArray(schema.enum)) {
590
+ return schema.enum.map((v) => `'${v}'`).join(" | ");
591
+ }
592
+ return "string";
593
+ }
594
+ if (schema.type === "number" || schema.type === "integer") {
595
+ return "number";
596
+ }
597
+ if (schema.type === "boolean") {
598
+ return "boolean";
599
+ }
600
+ if (schema.type === "array") {
601
+ return `${this.schemaToTypeString(schema.items)}[]`;
602
+ }
603
+ return "unknown";
604
+ }
605
+ };
606
+
607
+ // src/codegen/generator.ts
608
+ var CodeGenerator = class {
609
+ config;
610
+ constructor(config) {
611
+ this.config = config;
612
+ }
613
+ /**
614
+ * Generate TypeScript and Zod code from a parsed OpenAPI spec.
615
+ */
616
+ generate(spec) {
617
+ const types = this.generateTypes(spec);
618
+ const zod = this.config.includeZod ? this.generateZod(spec) : void 0;
619
+ const barrel = this.generateBarrel(spec);
620
+ return { types, zod, barrel };
621
+ }
622
+ /**
623
+ * Generate TypeScript type definitions.
624
+ */
625
+ generateTypes(spec) {
626
+ const lines = [];
627
+ lines.push("/**");
628
+ lines.push(` * Generated from ${spec.title} v${spec.version}`);
629
+ if (spec.description) {
630
+ lines.push(` * ${spec.description}`);
631
+ }
632
+ lines.push(" */");
633
+ lines.push("");
634
+ for (const schema of spec.schemas) {
635
+ if (this.config.includeJSDoc) {
636
+ lines.push("/**");
637
+ lines.push(` * ${schema.description || schema.name}`);
638
+ lines.push(" */");
639
+ }
640
+ lines.push(...this.generateSchemaType(schema));
641
+ lines.push("");
642
+ }
643
+ return lines.join("\n");
644
+ }
645
+ /**
646
+ * Generate a single schema as a TypeScript type.
647
+ */
648
+ generateSchemaType(schema) {
649
+ const lines = [];
650
+ if (schema.type === "enum") {
651
+ lines.push(`export type ${schema.name} = ${schema.enumValues?.map((v) => `'${v}'`).join(" | ")};`);
652
+ return lines;
653
+ }
654
+ if (schema.type === "object" && schema.properties) {
655
+ lines.push(`export interface ${schema.name} {`);
656
+ for (const [propName, prop] of Object.entries(schema.properties)) {
657
+ if (this.config.includeJSDoc && prop.description) {
658
+ lines.push(` /** ${prop.description} */`);
659
+ }
660
+ const optional = prop.required ? "" : "?";
661
+ const typeStr = this.schemaPropertyToTypeString(prop.schema);
662
+ lines.push(` ${propName}${optional}: ${typeStr};`);
663
+ }
664
+ lines.push("}");
665
+ return lines;
666
+ }
667
+ if (schema.type === "array" && schema.items) {
668
+ const itemType = this.schemaToTypeString(schema.items);
669
+ lines.push(`export type ${schema.name} = ${itemType}[];`);
670
+ return lines;
671
+ }
672
+ lines.push(`export type ${schema.name} = unknown;`);
673
+ return lines;
674
+ }
675
+ /**
676
+ * Generate Zod schemas.
677
+ */
678
+ generateZod(spec) {
679
+ const lines = [];
680
+ lines.push('import { z } from "zod";');
681
+ lines.push("");
682
+ for (const schema of spec.schemas) {
683
+ lines.push(`export const ${this.toSchemeName(schema.name)} = z.`);
684
+ lines.push(...this.generateZodSchema(schema, ""));
685
+ lines.push(";");
686
+ lines.push("");
687
+ }
688
+ return lines.join("\n");
689
+ }
690
+ /**
691
+ * Generate a Zod schema definition.
692
+ */
693
+ generateZodSchema(schema, indent) {
694
+ const lines = [];
695
+ if (schema.type === "enum" && schema.enumValues) {
696
+ const values = schema.enumValues.map((v) => `'${v}'`).join(", ");
697
+ lines.push(`enum([${values}])`);
698
+ return lines;
699
+ }
700
+ if (schema.type === "object" && schema.properties) {
701
+ lines.push("object({");
702
+ for (const [propName, prop] of Object.entries(schema.properties)) {
703
+ const propZod = this.generateZodSchemaForProperty(prop);
704
+ const optional = prop.required ? "" : ".optional()";
705
+ lines.push(` ${propName}: ${propZod}${optional},`);
706
+ }
707
+ lines.push("})");
708
+ return lines;
709
+ }
710
+ if (schema.type === "array" && schema.items) {
711
+ const itemZod = this.schemaToZodString(schema.items);
712
+ lines.push(`array(${itemZod})`);
713
+ return lines;
714
+ }
715
+ lines.push("unknown()");
716
+ return lines;
717
+ }
718
+ /**
719
+ * Generate Zod for a property within an object.
720
+ */
721
+ generateZodSchemaForProperty(prop) {
722
+ return this.schemaToZodString(prop.schema);
723
+ }
724
+ /**
725
+ * Convert a schema to a Zod schema string.
726
+ */
727
+ schemaToZodString(schema) {
728
+ switch (schema.type) {
729
+ case "string":
730
+ return "z.string()";
731
+ case "number":
732
+ return "z.number()";
733
+ case "boolean":
734
+ return "z.boolean()";
735
+ case "enum":
736
+ if (schema.enumValues) {
737
+ const values = schema.enumValues.map((v) => `'${v}'`).join(", ");
738
+ return `z.enum([${values}])`;
739
+ }
740
+ return "z.string()";
741
+ case "array":
742
+ if (schema.items) {
743
+ const itemZod = this.schemaToZodString(schema.items);
744
+ return `z.array(${itemZod})`;
745
+ }
746
+ return "z.array(z.unknown())";
747
+ case "object":
748
+ if (schema.properties) {
749
+ const entries = Object.entries(schema.properties).map(([name, prop]) => {
750
+ const propZod = this.schemaToZodString(prop.schema);
751
+ const optional = prop.required ? "" : ".optional()";
752
+ return `${name}: ${propZod}${optional}`;
753
+ }).join(", ");
754
+ return `z.object({ ${entries} })`;
755
+ }
756
+ return "z.object({})";
757
+ default:
758
+ return "z.unknown()";
759
+ }
760
+ }
761
+ /**
762
+ * Convert a schema to a TypeScript type string.
763
+ */
764
+ schemaToTypeString(schema) {
765
+ switch (schema.type) {
766
+ case "string":
767
+ return "string";
768
+ case "number":
769
+ return "number";
770
+ case "boolean":
771
+ return "boolean";
772
+ case "enum":
773
+ return schema.enumValues?.map((v) => `'${v}'`).join(" | ") || "string";
774
+ case "array":
775
+ return schema.items ? `${this.schemaToTypeString(schema.items)}[]` : "unknown[]";
776
+ case "object":
777
+ if (schema.isReference) {
778
+ return schema.name;
779
+ }
780
+ if (schema.properties) {
781
+ const entries = Object.entries(schema.properties).map(([name, prop]) => {
782
+ const optional = prop.required ? "" : "?";
783
+ const typeStr = this.schemaToTypeString(prop.schema);
784
+ return `${name}${optional}: ${typeStr}`;
785
+ }).join("; ");
786
+ return `{ ${entries} }`;
787
+ }
788
+ return "object";
789
+ default:
790
+ return "unknown";
791
+ }
792
+ }
793
+ /**
794
+ * Convert a schema property to a TypeScript type string.
795
+ */
796
+ schemaPropertyToTypeString(schema) {
797
+ return this.schemaToTypeString(schema);
798
+ }
799
+ /**
800
+ * Generate a barrel export file.
801
+ */
802
+ generateBarrel(spec) {
803
+ const lines = [];
804
+ lines.push("/**");
805
+ lines.push(` * Generated barrel export for ${spec.title}`);
806
+ lines.push(" */");
807
+ lines.push("");
808
+ for (const schema of spec.schemas) {
809
+ lines.push(`export type { ${schema.name} } from './${this.toFileName(schema.name)}';`);
810
+ }
811
+ return lines.join("\n");
812
+ }
813
+ /**
814
+ * Convert a schema name to a file name.
815
+ */
816
+ toFileName(name) {
817
+ return name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
818
+ }
819
+ /**
820
+ * Convert a schema name to a Zod schema name.
821
+ */
822
+ toSchemeName(name) {
823
+ return `${name.charAt(0).toLowerCase()}${name.slice(1)}Schema`;
824
+ }
825
+ };
826
+
827
+ // src/commands/codegen.ts
828
+ function createCodegenCommand() {
829
+ return new Command2("codegen").description("Generate TypeScript types from OpenAPI specifications").argument("<spec>", "Path or URL to OpenAPI spec (JSON or YAML)").option("--output <dir>", "Output directory", "./src/generated").option("--zod", "Include Zod schemas", false).option("--jsdoc", "Include JSDoc comments", true).option("--package-name <name>", "Package name for imports", void 0).action(async (spec, options) => {
830
+ try {
831
+ await runCodegen(spec, options);
832
+ } catch (error2) {
833
+ const message = error2 instanceof Error ? error2.message : String(error2);
834
+ console.error(`Error: ${message}`);
835
+ process.exit(1);
836
+ }
837
+ });
838
+ }
839
+ async function runCodegen(specInput, options) {
840
+ let specContent;
841
+ if (specInput.startsWith("http://") || specInput.startsWith("https://")) {
842
+ specContent = await fetchSpec(specInput);
843
+ } else {
844
+ specContent = fs3.readFileSync(specInput, "utf-8");
845
+ }
846
+ let specObj;
847
+ try {
848
+ specObj = JSON.parse(specContent);
849
+ } catch {
850
+ throw new Error("Failed to parse spec \u2014 ensure it is valid JSON (YAML not yet supported)");
851
+ }
852
+ const parser = new OpenAPIParser();
853
+ const parsedSpec = parser.parse(specObj);
854
+ const config = {
855
+ outputDir: options.output,
856
+ includeZod: options.zod === true,
857
+ includeJSDoc: options.jsdoc !== false,
858
+ packageName: options.packageName
859
+ };
860
+ if (!fs3.existsSync(config.outputDir)) {
861
+ fs3.mkdirSync(config.outputDir, { recursive: true });
862
+ }
863
+ const generator = new CodeGenerator(config);
864
+ const generated = generator.generate(parsedSpec);
865
+ const typesPath = path3.join(config.outputDir, "index.ts");
866
+ fs3.writeFileSync(typesPath, generated.types, "utf-8");
867
+ console.log(`\u2713 Generated types: ${typesPath}`);
868
+ if (generated.zod && config.includeZod) {
869
+ const zodPath = path3.join(config.outputDir, "schemas.ts");
870
+ fs3.writeFileSync(zodPath, generated.zod, "utf-8");
871
+ console.log(`\u2713 Generated Zod schemas: ${zodPath}`);
872
+ }
873
+ if (generated.barrel) {
874
+ const barrelPath = path3.join(config.outputDir, "index.ts");
875
+ fs3.appendFileSync(barrelPath, "\n\n" + generated.barrel, "utf-8");
876
+ }
877
+ console.log(`
878
+ Codegen complete. Generated ${config.includeZod ? "types and schemas" : "types"} in ${config.outputDir}`);
879
+ }
880
+ async function fetchSpec(url) {
881
+ try {
882
+ const response = await fetch(url);
883
+ if (!response.ok) {
884
+ throw new Error(`HTTP ${response.status}`);
885
+ }
886
+ return await response.text();
887
+ } catch (error2) {
888
+ throw new Error(`Failed to fetch spec from ${url}: ${error2 instanceof Error ? error2.message : String(error2)}`);
889
+ }
890
+ }
891
+
892
+ // src/commands/inspect.ts
893
+ import fs4 from "fs";
894
+ import { Command as Command3 } from "commander";
895
+
896
+ // src/inspect/routes.ts
897
+ var ROUTES = {
898
+ sessions: {
899
+ list: {
900
+ method: "GET",
901
+ path: "/ocapi/v1/sessions",
902
+ description: "List sessions with optional site and date filters"
903
+ },
904
+ get: {
905
+ method: "GET",
906
+ path: "/ocapi/v1/sessions/:id",
907
+ description: "Get a specific session by ID"
908
+ },
909
+ search: {
910
+ method: "GET",
911
+ path: "/ocapi/v1/sessions",
912
+ description: "Search sessions by film, site, or date"
913
+ }
914
+ },
915
+ sites: {
916
+ list: {
917
+ method: "GET",
918
+ path: "/ocapi/v1/sites",
919
+ description: "List all cinema sites"
920
+ },
921
+ get: {
922
+ method: "GET",
923
+ path: "/ocapi/v1/sites/:id",
924
+ description: "Get detailed site information"
925
+ },
926
+ search: {
927
+ method: "GET",
928
+ path: "/ocapi/v1/sites",
929
+ description: "Search sites by name or location"
930
+ }
931
+ },
932
+ films: {
933
+ list: {
934
+ method: "GET",
935
+ path: "/ocapi/v1/films",
936
+ description: "List films \u2014 now showing and coming soon"
937
+ },
938
+ get: {
939
+ method: "GET",
940
+ path: "/ocapi/v1/films/:id",
941
+ description: "Get full film details with cast and crew"
942
+ },
943
+ search: {
944
+ method: "GET",
945
+ path: "/ocapi/v1/films",
946
+ description: "Search films by title, genre, or rating"
947
+ }
948
+ },
949
+ orders: {
950
+ list: {
951
+ method: "GET",
952
+ path: "/ocapi/v1/orders",
953
+ description: "List orders for a member or site"
954
+ },
955
+ get: {
956
+ method: "GET",
957
+ path: "/ocapi/v1/orders/:id",
958
+ description: "Get order details with line items"
959
+ },
960
+ search: {
961
+ method: "GET",
962
+ path: "/ocapi/v1/orders",
963
+ description: "Search orders by date range or status"
964
+ }
965
+ },
966
+ loyalty: {
967
+ list: {
968
+ method: "GET",
969
+ path: "/ocapi/v1/loyalty/members",
970
+ description: "List loyalty members"
971
+ },
972
+ get: {
973
+ method: "GET",
974
+ path: "/ocapi/v1/loyalty/members/:id",
975
+ description: "Get loyalty member profile and points balance"
976
+ },
977
+ search: {
978
+ method: "GET",
979
+ path: "/ocapi/v1/loyalty/members",
980
+ description: "Search loyalty members by name or email"
981
+ }
982
+ }
983
+ };
984
+ var VALID_RESOURCES = Object.keys(ROUTES);
985
+ var VALID_ACTIONS = ["list", "get", "search"];
986
+ function resolveRoute(resource, action) {
987
+ const resourceRoutes = ROUTES[resource];
988
+ if (!resourceRoutes) return null;
989
+ const route = resourceRoutes[action];
990
+ return route ?? null;
991
+ }
992
+ function buildRequestUrl(route, baseUrl, options, id) {
993
+ let path4 = route.path;
994
+ if (path4.includes(":id")) {
995
+ if (!id) {
996
+ throw new Error(
997
+ `Route ${route.method} ${route.path} requires an id argument`
998
+ );
999
+ }
1000
+ path4 = path4.replace(":id", encodeURIComponent(id));
1001
+ }
1002
+ const url = new URL(path4, baseUrl);
1003
+ if (options.site) {
1004
+ url.searchParams.set("siteId", options.site);
1005
+ }
1006
+ if (options.date) {
1007
+ url.searchParams.set("date", options.date);
1008
+ }
1009
+ if (options.query) {
1010
+ url.searchParams.set("q", options.query);
1011
+ }
1012
+ if (options.limit) {
1013
+ url.searchParams.set("limit", String(options.limit));
1014
+ }
1015
+ return url.toString();
1016
+ }
1017
+
1018
+ // src/inspect/formatter.ts
1019
+ import chalk2 from "chalk";
1020
+ function formatResponse(data, options = {}) {
1021
+ const parts = [];
1022
+ if (options.method || options.url || options.statusCode) {
1023
+ parts.push(formatResponseHeader(options));
1024
+ parts.push("");
1025
+ }
1026
+ parts.push(highlightJSON(data, options));
1027
+ if (options.timing) {
1028
+ const durationMs = options.timing.endMs - options.timing.startMs;
1029
+ parts.push("");
1030
+ parts.push(chalk2.dim(`\u23F1 ${formatDuration(durationMs)}`));
1031
+ }
1032
+ return parts.join("\n");
1033
+ }
1034
+ function highlightJSON(data, options = {}) {
1035
+ const indent = options.indent ?? 2;
1036
+ const useColor = options.color !== false;
1037
+ const maxStr = options.maxStringLength ?? 120;
1038
+ if (!useColor) {
1039
+ return JSON.stringify(data, null, indent);
1040
+ }
1041
+ return colorize(data, 0, indent, maxStr);
1042
+ }
1043
+ function formatResponseHeader(options) {
1044
+ const parts = [];
1045
+ if (options.method) {
1046
+ parts.push(chalk2.bold.white(options.method.toUpperCase()));
1047
+ }
1048
+ if (options.url) {
1049
+ parts.push(chalk2.cyan.underline(options.url));
1050
+ }
1051
+ if (options.statusCode) {
1052
+ const statusStr = String(options.statusCode);
1053
+ if (options.statusCode >= 200 && options.statusCode < 300) {
1054
+ parts.push(chalk2.green(`${statusStr} OK`));
1055
+ } else if (options.statusCode >= 400 && options.statusCode < 500) {
1056
+ parts.push(chalk2.yellow(`${statusStr} Client Error`));
1057
+ } else if (options.statusCode >= 500) {
1058
+ parts.push(chalk2.red(`${statusStr} Server Error`));
1059
+ } else {
1060
+ parts.push(chalk2.dim(statusStr));
1061
+ }
1062
+ }
1063
+ if (options.timing) {
1064
+ const durationMs = options.timing.endMs - options.timing.startMs;
1065
+ parts.push(chalk2.dim(`(${formatDuration(durationMs)})`));
1066
+ }
1067
+ return parts.join(" ");
1068
+ }
1069
+ function colorize(value, depth, indent, maxStr) {
1070
+ const pad = " ".repeat(depth * indent);
1071
+ const innerPad = " ".repeat((depth + 1) * indent);
1072
+ if (value === null) {
1073
+ return chalk2.dim("null");
1074
+ }
1075
+ if (value === void 0) {
1076
+ return chalk2.dim("undefined");
1077
+ }
1078
+ if (typeof value === "string") {
1079
+ const truncated = value.length > maxStr ? value.slice(0, maxStr) : value;
1080
+ const display = value.length > maxStr ? JSON.stringify(truncated).slice(0, -1) + chalk2.dim('\u2026"') : JSON.stringify(truncated);
1081
+ return chalk2.green(display);
1082
+ }
1083
+ if (typeof value === "number") {
1084
+ return chalk2.cyan(String(value));
1085
+ }
1086
+ if (typeof value === "boolean") {
1087
+ return chalk2.yellow(String(value));
1088
+ }
1089
+ if (Array.isArray(value)) {
1090
+ if (value.length === 0) {
1091
+ return chalk2.dim("[]");
1092
+ }
1093
+ const items = value.map(
1094
+ (item) => `${innerPad}${colorize(item, depth + 1, indent, maxStr)}`
1095
+ );
1096
+ return [
1097
+ chalk2.dim("["),
1098
+ items.join(chalk2.dim(",") + "\n"),
1099
+ `${pad}${chalk2.dim("]")}`
1100
+ ].join("\n");
1101
+ }
1102
+ if (typeof value === "object") {
1103
+ const entries = Object.entries(value);
1104
+ if (entries.length === 0) {
1105
+ return chalk2.dim("{}");
1106
+ }
1107
+ const lines = entries.map(([key, val]) => {
1108
+ const colorizedKey = chalk2.bold.white(`"${key}"`);
1109
+ const colorizedVal = colorize(val, depth + 1, indent, maxStr);
1110
+ return `${innerPad}${colorizedKey}${chalk2.dim(":")} ${colorizedVal}`;
1111
+ });
1112
+ return [
1113
+ chalk2.dim("{"),
1114
+ lines.join(chalk2.dim(",") + "\n"),
1115
+ `${pad}${chalk2.dim("}")}`
1116
+ ].join("\n");
1117
+ }
1118
+ return String(value);
1119
+ }
1120
+ function formatDuration(ms) {
1121
+ if (ms < 1e3) {
1122
+ return `${Math.round(ms)}ms`;
1123
+ }
1124
+ if (ms < 6e4) {
1125
+ return `${(ms / 1e3).toFixed(1)}s`;
1126
+ }
1127
+ const minutes = Math.floor(ms / 6e4);
1128
+ const seconds = Math.round(ms % 6e4 / 1e3);
1129
+ return `${minutes}m ${seconds}s`;
1130
+ }
1131
+ function formatCount(data) {
1132
+ if (Array.isArray(data)) {
1133
+ return chalk2.dim(`(${data.length} item${data.length === 1 ? "" : "s"})`);
1134
+ }
1135
+ if (typeof data === "object" && data !== null) {
1136
+ const count = Object.keys(data).length;
1137
+ return chalk2.dim(`(${count} field${count === 1 ? "" : "s"})`);
1138
+ }
1139
+ return "";
1140
+ }
1141
+ function formatTable(rows, columns) {
1142
+ if (rows.length === 0) {
1143
+ return chalk2.dim("(no results)");
1144
+ }
1145
+ const cols = columns ?? Object.keys(rows[0]);
1146
+ const widths = cols.map((col) => {
1147
+ const values = rows.map((row) => String(row[col] ?? ""));
1148
+ return Math.max(col.length, ...values.map((v) => v.length));
1149
+ });
1150
+ const header = cols.map((col, i) => chalk2.bold(col.padEnd(widths[i]))).join(" ");
1151
+ const separator = widths.map((w) => chalk2.dim("\u2500".repeat(w))).join(" ");
1152
+ const bodyLines = rows.map(
1153
+ (row) => cols.map((col, i) => String(row[col] ?? "").padEnd(widths[i])).join(" ")
1154
+ );
1155
+ return [header, separator, ...bodyLines].join("\n");
1156
+ }
1157
+
1158
+ // src/commands/inspect.ts
1159
+ var DEFAULT_API_URL = "https://api.vista.co";
1160
+ function createInspectCommand() {
1161
+ return new Command3("inspect").description("Interactive API explorer with formatted output").argument("<resource>", `API resource: ${VALID_RESOURCES.join(", ")}`).argument("<action>", `Action: ${VALID_ACTIONS.join(", ")}`).argument("[id]", 'Resource ID (required for "get" action)').option("--site <id>", "Cinema site ID for scoping requests").option("--date <date>", "Date filter (YYYY-MM-DD)").option("-q, --query <text>", "Search query string").option("-o, --output <file>", "Save response to file (JSON)").option("-n, --limit <count>", "Maximum results to return", parseInt).option("--table", "Display results as a table (for list actions)").action(async function(resource, action, id, cmdOptions, command) {
1162
+ try {
1163
+ await runInspect(resource, action, id, cmdOptions, command);
1164
+ } catch (error2) {
1165
+ const message = error2 instanceof Error ? error2.message : String(error2);
1166
+ console.error(error(message));
1167
+ process.exit(1);
1168
+ }
1169
+ });
1170
+ }
1171
+ async function runInspect(resource, action, id, cmdOptions, command) {
1172
+ if (!VALID_RESOURCES.includes(resource)) {
1173
+ throw new Error(
1174
+ `Unknown resource "${resource}". Valid resources: ${VALID_RESOURCES.join(", ")}`
1175
+ );
1176
+ }
1177
+ if (!VALID_ACTIONS.includes(action)) {
1178
+ throw new Error(
1179
+ `Unknown action "${action}". Valid actions: ${VALID_ACTIONS.join(", ")}`
1180
+ );
1181
+ }
1182
+ if (action === "get" && !id) {
1183
+ throw new Error(
1184
+ `The "get" action requires a resource ID. Usage: theatrical inspect ${resource} get <id>`
1185
+ );
1186
+ }
1187
+ const route = resolveRoute(resource, action);
1188
+ if (!route) {
1189
+ throw new Error(`No route defined for ${resource} ${action}`);
1190
+ }
1191
+ const options = {
1192
+ site: cmdOptions.site,
1193
+ date: cmdOptions.date,
1194
+ query: cmdOptions.query,
1195
+ output: cmdOptions.output,
1196
+ limit: cmdOptions.limit,
1197
+ noColor: cmdOptions.noColor
1198
+ };
1199
+ const parentOpts = command.parent?.opts() ?? {};
1200
+ const apiUrl = parentOpts.apiUrl ?? DEFAULT_API_URL;
1201
+ const apiKey = parentOpts.apiKey;
1202
+ const url = buildRequestUrl(route, apiUrl, options, id);
1203
+ console.log(heading(`${route.method} ${resource}/${action}`));
1204
+ console.log(dim(route.description));
1205
+ console.log(keyValue("URL", url));
1206
+ console.log("");
1207
+ const result = await executeRequest(url, route.method, apiKey);
1208
+ if (cmdOptions.table && Array.isArray(result.data)) {
1209
+ console.log(formatCount(result.data));
1210
+ console.log(formatTable(result.data));
1211
+ } else {
1212
+ const formatted = formatResponse(result.data, {
1213
+ color: !options.noColor,
1214
+ method: result.method,
1215
+ url: result.url,
1216
+ statusCode: result.status,
1217
+ timing: { startMs: 0, endMs: result.durationMs }
1218
+ });
1219
+ console.log(formatted);
1220
+ }
1221
+ if (options.output) {
1222
+ const json = JSON.stringify(result.data, null, 2);
1223
+ fs4.writeFileSync(options.output, json, "utf-8");
1224
+ console.log("");
1225
+ console.log(success(`Response saved to ${options.output}`));
1226
+ }
1227
+ }
1228
+ async function executeRequest(url, method, apiKey) {
1229
+ const headers = {
1230
+ "Accept": "application/json",
1231
+ "User-Agent": "theatrical-cli/0.1.0"
1232
+ };
1233
+ if (apiKey) {
1234
+ headers["Authorization"] = `Bearer ${apiKey}`;
1235
+ }
1236
+ const startMs = performance.now();
1237
+ const response = await fetch(url, {
1238
+ method,
1239
+ headers
1240
+ });
1241
+ const endMs = performance.now();
1242
+ const durationMs = endMs - startMs;
1243
+ let data;
1244
+ const contentType = response.headers.get("content-type") ?? "";
1245
+ if (contentType.includes("application/json")) {
1246
+ data = await response.json();
1247
+ } else {
1248
+ const text = await response.text();
1249
+ try {
1250
+ data = JSON.parse(text);
1251
+ } catch {
1252
+ data = { _raw: text };
1253
+ }
1254
+ }
1255
+ const responseHeaders = {};
1256
+ response.headers.forEach((value, key) => {
1257
+ responseHeaders[key] = value;
1258
+ });
1259
+ return {
1260
+ status: response.status,
1261
+ headers: responseHeaders,
1262
+ data,
1263
+ durationMs,
1264
+ url,
1265
+ method
1266
+ };
1267
+ }
1268
+
1269
+ // src/index.ts
1270
+ var VERSION = "0.1.0";
1271
+ function createProgram() {
1272
+ const program = new Command4();
1273
+ program.name("theatrical").description("Developer toolkit for cinema platform APIs").version(VERSION, "-v, --version", "Display the CLI version").option("--no-color", "Disable colored output").option("--verbose", "Enable verbose logging").option(
1274
+ "--format <format>",
1275
+ "Output format: json, table, or pretty",
1276
+ "pretty"
1277
+ ).option("--config <path>", "Path to config file").option("--api-url <url>", "Override the API base URL").option("--api-key <key>", "API authentication key").hook("preAction", async (thisCommand) => {
1278
+ const opts = thisCommand.opts();
1279
+ const config = await resolveConfig({
1280
+ verbose: opts.verbose ?? void 0,
1281
+ color: opts.color ?? void 0,
1282
+ outputFormat: opts.format ?? void 0,
1283
+ apiUrl: opts.apiUrl ?? void 0,
1284
+ apiKey: opts.apiKey ?? void 0
1285
+ });
1286
+ thisCommand.setOptionValue("resolvedConfig", config);
1287
+ if (config.verbose) {
1288
+ printBanner(VERSION);
1289
+ }
1290
+ });
1291
+ program.addCommand(createInitCommand());
1292
+ program.addCommand(createCodegenCommand());
1293
+ program.addCommand(createInspectCommand());
1294
+ return program;
1295
+ }
1296
+ async function run(argv = process.argv) {
1297
+ const program = createProgram();
1298
+ await program.parseAsync(argv);
1299
+ }
1300
+ export {
1301
+ createProgram,
1302
+ run
1303
+ };