@happyvertical/smrt-cli 0.37.2 → 0.37.3

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 CHANGED
@@ -6,1819 +6,1450 @@ import { fileURLToPath } from "node:url";
6
6
  import { ObjectRegistry } from "@happyvertical/smrt-core";
7
7
  import { loadLocalTestManifestSync } from "@happyvertical/smrt-core/manifest";
8
8
  import { parseCliArgs } from "@happyvertical/utils";
9
- const __dirname$1 = dirname(fileURLToPath(import.meta.url));
10
- const packageJson = JSON.parse(
11
- readFileSync(join(__dirname$1, "../package.json"), "utf-8")
12
- );
13
- const CLI_VERSION = packageJson.version;
9
+ //#region src/cli-generator.ts
10
+ /**
11
+ * CLI command generator for smrt objects
12
+ *
13
+ * Generates admin and development tools from object definitions
14
+ */
15
+ var __dirname = dirname(fileURLToPath(import.meta.url));
16
+ var CLI_VERSION = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8")).version;
17
+ /**
18
+ * Count required arguments in an args array.
19
+ * Arguments wrapped in [...] are optional, others are required.
20
+ * Examples:
21
+ * ['id'] -> 1 required
22
+ * ['[pattern...]'] -> 0 required (optional)
23
+ * ['id', '[options...]'] -> 1 required
24
+ */
14
25
  function countRequiredArgs(args) {
15
- if (!args) return 0;
16
- return args.filter((arg) => !arg.startsWith("[") || !arg.endsWith("]")).length;
26
+ if (!args) return 0;
27
+ return args.filter((arg) => !arg.startsWith("[") || !arg.endsWith("]")).length;
17
28
  }
18
- let _gnodeCommands = null;
19
- let _generateCommands = null;
20
- let _gitCommands = null;
21
- let _initCommands = null;
22
- let _utilityCommands = null;
23
- let _dispatchCommands = null;
24
- let _docsCommands = null;
25
- let _playgroundCommands = null;
29
+ var _gnodeCommands = null;
30
+ var _generateCommands = null;
31
+ var _gitCommands = null;
32
+ var _initCommands = null;
33
+ var _utilityCommands = null;
34
+ var _dispatchCommands = null;
35
+ var _docsCommands = null;
36
+ var _playgroundCommands = null;
26
37
  async function getGnodeCommands() {
27
- if (!_gnodeCommands) {
28
- const { gnodeCommands } = await import("./index-CZTaF6ei.js");
29
- _gnodeCommands = gnodeCommands;
30
- }
31
- return _gnodeCommands;
38
+ if (!_gnodeCommands) {
39
+ const { gnodeCommands } = await import("./commands-gnJG0EIL.js");
40
+ _gnodeCommands = gnodeCommands;
41
+ }
42
+ return _gnodeCommands;
32
43
  }
33
44
  async function getGitCommands() {
34
- if (!_gitCommands) {
35
- const { gitCommands } = await import("./index-CZTaF6ei.js");
36
- _gitCommands = gitCommands;
37
- }
38
- return _gitCommands;
45
+ if (!_gitCommands) {
46
+ const { gitCommands } = await import("./commands-gnJG0EIL.js");
47
+ _gitCommands = gitCommands;
48
+ }
49
+ return _gitCommands;
39
50
  }
40
51
  async function getGenerateCommands() {
41
- if (!_generateCommands) {
42
- const { generateCommands } = await import("./index-CZTaF6ei.js");
43
- _generateCommands = generateCommands;
44
- }
45
- return _generateCommands;
52
+ if (!_generateCommands) {
53
+ const { generateCommands } = await import("./commands-gnJG0EIL.js");
54
+ _generateCommands = generateCommands;
55
+ }
56
+ return _generateCommands;
46
57
  }
47
58
  async function getInitCommands() {
48
- if (!_initCommands) {
49
- const { initCommands } = await import("./index-CZTaF6ei.js");
50
- _initCommands = initCommands;
51
- }
52
- return _initCommands;
59
+ if (!_initCommands) {
60
+ const { initCommands } = await import("./commands-gnJG0EIL.js");
61
+ _initCommands = initCommands;
62
+ }
63
+ return _initCommands;
53
64
  }
54
65
  async function getUtilityCommands() {
55
- if (!_utilityCommands) {
56
- const { utilityCommands } = await import("./index-CZTaF6ei.js");
57
- _utilityCommands = utilityCommands;
58
- }
59
- return _utilityCommands;
66
+ if (!_utilityCommands) {
67
+ const { utilityCommands } = await import("./commands-gnJG0EIL.js");
68
+ _utilityCommands = utilityCommands;
69
+ }
70
+ return _utilityCommands;
60
71
  }
61
72
  async function getDispatchCommands() {
62
- if (!_dispatchCommands) {
63
- const { dispatchCommands } = await import("./index-CZTaF6ei.js");
64
- _dispatchCommands = dispatchCommands;
65
- }
66
- return _dispatchCommands;
73
+ if (!_dispatchCommands) {
74
+ const { dispatchCommands } = await import("./commands-gnJG0EIL.js");
75
+ _dispatchCommands = dispatchCommands;
76
+ }
77
+ return _dispatchCommands;
67
78
  }
68
79
  async function getDocsCommands() {
69
- if (!_docsCommands) {
70
- const { docsCommands } = await import("./index-CZTaF6ei.js");
71
- _docsCommands = docsCommands;
72
- }
73
- return _docsCommands;
80
+ if (!_docsCommands) {
81
+ const { docsCommands } = await import("./commands-gnJG0EIL.js");
82
+ _docsCommands = docsCommands;
83
+ }
84
+ return _docsCommands;
74
85
  }
75
86
  async function getPlaygroundCommands() {
76
- if (!_playgroundCommands) {
77
- const { playgroundCommands } = await import("./index-CZTaF6ei.js");
78
- _playgroundCommands = playgroundCommands;
79
- }
80
- return _playgroundCommands;
81
- }
82
- class CLIGenerator {
83
- config;
84
- context;
85
- collections = /* @__PURE__ */ new Map();
86
- commandCache = null;
87
- /** Lazy-loaded cache for object commands (key: objectName lowercase) */
88
- objectCommandsCache = /* @__PURE__ */ new Map();
89
- /** Set of registered object names (lowercase) for quick lookup */
90
- registeredObjectNames = null;
91
- /** Whether manifest/classes have been loaded */
92
- manifestLoaded = false;
93
- constructor(config = {}, context = {}) {
94
- this.config = {
95
- name: "smrt",
96
- version: CLI_VERSION,
97
- description: "Admin CLI for smrt objects",
98
- prompt: true,
99
- colors: true,
100
- ...config
101
- };
102
- this.context = context;
103
- }
104
- /**
105
- * Check if running in test environment
106
- */
107
- isTestMode() {
108
- const testGlobals = global;
109
- return process.env.NODE_ENV === "test" || process.env.VITEST === "true" || typeof testGlobals.it === "function" || typeof testGlobals.describe === "function";
110
- }
111
- /**
112
- * Check if a type string represents an inline object type parameter
113
- * e.g., "{ meetingId?: string; limit?: number }"
114
- *
115
- * Note: Only supports single-level nested braces. Deeply nested types
116
- * like "{ config: { nested: { deep: string } } }" are not fully supported.
117
- */
118
- isObjectTypeParameter(typeStr) {
119
- return typeStr.includes("{") && typeStr.includes("}") && typeStr.includes(":");
120
- }
121
- /**
122
- * Try to load user's compiled classes for runtime execution
123
- *
124
- * Loads both local classes (from project entry point) and external classes
125
- * (from packages in .smrt/manifest.json) to enable full CLI functionality.
126
- */
127
- async tryLoadUserClasses() {
128
- const verbose = process.env.SMRT_VERBOSE === "true" || process.env.DEBUG?.includes("smrt");
129
- if (verbose) {
130
- console.log("[CLI] tryLoadUserClasses() called");
131
- }
132
- try {
133
- if (verbose) {
134
- console.log("[CLI] Loading local classes...");
135
- }
136
- await this.loadLocalClasses();
137
- } catch (localError) {
138
- if (verbose) {
139
- console.log(
140
- "[CLI] Local class loading failed (this is OK if using external packages only):",
141
- localError instanceof Error ? localError.message : "Unknown error"
142
- );
143
- }
144
- }
145
- if (verbose) {
146
- console.log("[CLI] Loading external classes...");
147
- }
148
- await this.loadExternalClasses();
149
- const { getPackageConfig } = await import("@happyvertical/smrt-config");
150
- const { DEFAULT_CLI_CONFIG } = await import("./config-C8pQD-tk.js");
151
- const config = getPackageConfig("cli", DEFAULT_CLI_CONFIG);
152
- const registeredCount = ObjectRegistry.getAllClasses().size;
153
- if (verbose || config.verbose) {
154
- console.log(`[CLI] Successfully loaded ${registeredCount} SMRT objects`);
155
- }
156
- }
157
- /**
158
- * Load classes from local project entry point
159
- *
160
- * Entry point discovery order:
161
- * 1. smrt.config.js: packages.cli.entryPoint (explicit override)
162
- * 2. package.json: exports['.'] or main field
163
- * 3. Fallback: ./dist/index.js
164
- */
165
- async loadLocalClasses() {
166
- const { getPackageConfig } = await import("@happyvertical/smrt-config");
167
- const { DEFAULT_CLI_CONFIG } = await import("./config-C8pQD-tk.js");
168
- const fs = await import("node:fs");
169
- const path = await import("node:path");
170
- const config = getPackageConfig("cli", DEFAULT_CLI_CONFIG);
171
- let entryPoint = config.entryPoint;
172
- if (!entryPoint) {
173
- try {
174
- const packageJsonPath = path.resolve(process.cwd(), "package.json");
175
- if (fs.existsSync(packageJsonPath)) {
176
- const packageJson2 = JSON.parse(
177
- fs.readFileSync(packageJsonPath, "utf-8")
178
- );
179
- entryPoint = packageJson2.exports?.["."]?.import || packageJson2.exports?.["."] || packageJson2.main || "./dist/index.js";
180
- if (config.verbose) {
181
- console.log(
182
- `[CLI] Detected entry point from package.json: ${entryPoint}`
183
- );
184
- }
185
- }
186
- } catch {
187
- entryPoint = "./dist/index.js";
188
- }
189
- }
190
- if (!entryPoint) {
191
- entryPoint = "./dist/index.js";
192
- }
193
- const fullPath = path.resolve(process.cwd(), entryPoint);
194
- if (!fs.existsSync(fullPath)) {
195
- if (config.verbose) {
196
- console.log(`[CLI] Entry point not found: ${fullPath}`);
197
- }
198
- return;
199
- }
200
- if (config.verbose) {
201
- console.log(`[CLI] Loading local SMRT classes from ${entryPoint}...`);
202
- }
203
- const fileUrl = `file://${fullPath}`;
204
- const importedModule = await import(fileUrl);
205
- for (const [exportName, exportValue] of Object.entries(importedModule)) {
206
- if (exportValue && typeof exportValue === "function") {
207
- const itemClass = exportValue._itemClass;
208
- if (itemClass) {
209
- const tableName = itemClass.SMRT_TABLE_NAME || itemClass.name.toLowerCase();
210
- const existing = ObjectRegistry.getClass(tableName);
211
- if (existing && !existing.collectionConstructor) {
212
- ObjectRegistry.registerCollection(
213
- tableName,
214
- exportValue
215
- );
216
- if (config.verbose) {
217
- console.log(`[CLI] Registered local collection ${exportName}`);
218
- }
219
- }
220
- }
221
- }
222
- }
223
- }
224
- /**
225
- * Load classes from external packages
226
- *
227
- * Imports the auto-generated .smrt/register.js file which contains
228
- * static imports and registrations for all external SMRT objects.
229
- *
230
- * This file is generated by the consumer plugin during build.
231
- */
232
- async loadExternalClasses() {
233
- const { getPackageConfig } = await import("@happyvertical/smrt-config");
234
- const { DEFAULT_CLI_CONFIG } = await import("./config-C8pQD-tk.js");
235
- const { loadLocalTestManifestSync: loadLocalTestManifestSync2 } = await import("@happyvertical/smrt-core/manifest");
236
- const fs = await import("node:fs");
237
- const path = await import("node:path");
238
- const config = getPackageConfig("cli", DEFAULT_CLI_CONFIG);
239
- loadLocalTestManifestSync2();
240
- const registerPath = path.join(process.cwd(), ".smrt", "register.js");
241
- if (!fs.existsSync(registerPath)) {
242
- console.log(
243
- "[CLI] No .smrt/register.js found - custom commands may not work"
244
- );
245
- console.log(' Run "npm run build" to generate class registrations');
246
- return;
247
- }
248
- try {
249
- const fileUrl = `file://${registerPath}`;
250
- await import(fileUrl);
251
- if (config.verbose) {
252
- const count = ObjectRegistry.getAllClasses().size;
253
- console.log(`[CLI] Loaded ${count} objects from .smrt/register.js`);
254
- }
255
- } catch (error) {
256
- const msg = error instanceof Error ? error.message : "Unknown error";
257
- console.error(`
258
- ❌ Failed to load .smrt/register.js: ${msg}`);
259
- console.error(
260
- "\nThis usually means an installed package has non-Node.js exports (e.g., .svelte files)."
261
- );
262
- console.error("Fix the offending package, then re-run.\n");
263
- throw error;
264
- }
265
- }
266
- /**
267
- * Handle exits safely in test mode
268
- */
269
- exitWithError(message, code = 1) {
270
- if (this.isTestMode()) {
271
- throw new Error(message);
272
- }
273
- console.error(message);
274
- process.exit(code);
275
- }
276
- /**
277
- * Generate CLI handler function
278
- */
279
- generateHandler() {
280
- return async (argv) => {
281
- const commands = await this.generateCommands();
282
- const processedArgv = this.preprocessObjectCommands(argv, commands);
283
- const parsed = parseCliArgs(processedArgv, commands, {});
284
- await this.executeCommand(parsed, commands, processedArgv);
285
- };
286
- }
287
- /**
288
- * Preprocess argv to support space-separated object commands
289
- *
290
- * Converts: ['council', 'list'] → ['council:list']
291
- * Converts: ['council', 'get', 'abc-123'] → ['council:get', 'abc-123']
292
- *
293
- * This enables users to type:
294
- * smrt council list
295
- * smrt council get abc-123
296
- * smrt council analyze abc-123 --depth 3
297
- *
298
- * Instead of:
299
- * smrt council:list
300
- * smrt council:get abc-123
301
- */
302
- preprocessObjectCommands(argv, commands) {
303
- if (argv.length < 2) return argv;
304
- const firstArg = argv[0];
305
- const secondArg = argv[1];
306
- if (firstArg.includes(":")) return argv;
307
- if (firstArg.startsWith("-")) return argv;
308
- if (secondArg.startsWith("-")) return argv;
309
- const combinedCommand = `${firstArg}:${secondArg}`;
310
- const matchesCommand = commands.some(
311
- (cmd) => cmd.name === combinedCommand || cmd.aliases?.includes(combinedCommand)
312
- );
313
- if (matchesCommand) {
314
- return [combinedCommand, ...argv.slice(2)];
315
- }
316
- const knownBuiltInNamespaces = /* @__PURE__ */ new Set([
317
- "dispatch",
318
- "docs",
319
- "git",
320
- "playground"
321
- ]);
322
- if (knownBuiltInNamespaces.has(firstArg)) {
323
- return [combinedCommand, ...argv.slice(2)];
324
- }
325
- const registeredClasses = ObjectRegistry.getAllClasses();
326
- const isKnownObject = Array.from(registeredClasses.values()).some(
327
- (info) => (info.name || "").toLowerCase() === firstArg.toLowerCase()
328
- );
329
- if (isKnownObject) {
330
- return [combinedCommand, ...argv.slice(2)];
331
- }
332
- return argv;
333
- }
334
- /**
335
- * Ensure manifest and user classes are loaded.
336
- * This is the minimum required work before any command can be executed.
337
- * Separated from command generation to enable lazy command loading.
338
- */
339
- async ensureManifestLoaded() {
340
- if (this.manifestLoaded) {
341
- return;
342
- }
343
- const timing = this.context.timing;
344
- const verbose = process.env.SMRT_VERBOSE === "true" || process.env.DEBUG?.includes("smrt");
345
- const manifest = loadLocalTestManifestSync();
346
- if (verbose) {
347
- console.log(
348
- "[CLI] Manifest loaded:",
349
- manifest ? `${Object.keys(manifest.objects || {}).length} objects` : "null"
350
- );
351
- }
352
- if (manifest?.objects) {
353
- for (const [name, objectDef] of Object.entries(manifest.objects)) {
354
- ObjectRegistry.registerFromManifest(
355
- name,
356
- objectDef,
357
- manifest.packageName
358
- );
359
- }
360
- }
361
- const classLoadStart = timing ? performance.now() : 0;
362
- await this.tryLoadUserClasses();
363
- if (timing) {
364
- timing.classLoading = performance.now() - classLoadStart;
365
- }
366
- const registeredClasses = ObjectRegistry.getAllClasses();
367
- this.registeredObjectNames = new Set(
368
- Array.from(registeredClasses.values()).map(
369
- (info) => (info.name || "").toLowerCase()
370
- )
371
- );
372
- this.manifestLoaded = true;
373
- }
374
- /**
375
- * Get commands for a specific object (lazy generation with caching)
376
- */
377
- async getObjectCommandsLazy(objectName) {
378
- const lowerName = objectName.toLowerCase();
379
- const cachedCommands = this.objectCommandsCache.get(lowerName);
380
- if (cachedCommands) {
381
- return cachedCommands;
382
- }
383
- const registeredClasses = ObjectRegistry.getAllClasses();
384
- let actualName;
385
- let matchedKey;
386
- for (const [key, info] of registeredClasses) {
387
- if ((info.name || key).toLowerCase() === lowerName) {
388
- actualName = info.name || key;
389
- matchedKey = key;
390
- break;
391
- }
392
- }
393
- if (!actualName || !matchedKey) {
394
- return [];
395
- }
396
- const classInfo = registeredClasses.get(matchedKey);
397
- const commands = await this.generateObjectCommands(actualName, classInfo);
398
- this.objectCommandsCache.set(lowerName, commands);
399
- return commands;
400
- }
401
- /**
402
- * Find an object command by name (lazy lookup)
403
- */
404
- async findObjectCommand(commandName) {
405
- const colonIndex = commandName.indexOf(":");
406
- if (colonIndex === -1) {
407
- return void 0;
408
- }
409
- const objectName = commandName.slice(0, colonIndex);
410
- await this.ensureManifestLoaded();
411
- if (!this.registeredObjectNames?.has(objectName.toLowerCase())) {
412
- return void 0;
413
- }
414
- const objectCommands = await this.getObjectCommandsLazy(objectName);
415
- return objectCommands.find(
416
- (cmd) => cmd.name === commandName || cmd.aliases?.includes(commandName)
417
- );
418
- }
419
- /**
420
- * Generate all CLI commands
421
- *
422
- * NOTE: Object commands are now loaded LAZILY for better startup performance.
423
- * This method only generates utility commands upfront. Object commands are
424
- * generated on-demand when executeCommand() looks for them.
425
- *
426
- * For full command list (e.g., help display), use generateAllCommands().
427
- */
428
- async generateCommands() {
429
- const timing = this.context.timing;
430
- const verbose = process.env.SMRT_VERBOSE === "true" || process.env.DEBUG?.includes("smrt");
431
- if (this.commandCache) {
432
- if (verbose) {
433
- console.log("[CLI] generateCommands() returning cached commands");
434
- }
435
- return this.commandCache;
436
- }
437
- if (verbose) {
438
- console.log("[CLI] generateCommands() starting (lazy mode)");
439
- }
440
- await this.ensureManifestLoaded();
441
- const commandGenStart = timing ? performance.now() : 0;
442
- const commands = [];
443
- const commandNames = /* @__PURE__ */ new Set();
444
- for (const cmd of this.generateUtilityCommands()) {
445
- if (commandNames.has(cmd.name)) {
446
- if (verbose) {
447
- console.warn(`[CLI] Skipping duplicate utility command: ${cmd.name}`);
448
- }
449
- continue;
450
- }
451
- commandNames.add(cmd.name);
452
- commands.push(cmd);
453
- }
454
- if (timing) {
455
- timing.commandGen = performance.now() - commandGenStart;
456
- }
457
- this.commandCache = commands;
458
- return commands;
459
- }
460
- /**
461
- * Generate ALL commands including lazy-loaded object commands.
462
- * Used for help display where we need the complete list.
463
- */
464
- async generateAllCommands() {
465
- const verbose = process.env.SMRT_VERBOSE === "true" || process.env.DEBUG?.includes("smrt");
466
- if (verbose) {
467
- console.log("[CLI] generateAllCommands() - loading all object commands");
468
- }
469
- await this.ensureManifestLoaded();
470
- const allCommands = [];
471
- const commandNames = /* @__PURE__ */ new Set();
472
- for (const cmd of this.generateUtilityCommands()) {
473
- if (!commandNames.has(cmd.name)) {
474
- commandNames.add(cmd.name);
475
- allCommands.push(cmd);
476
- }
477
- }
478
- const registeredClasses = ObjectRegistry.getAllClasses();
479
- for (const [_key, classInfo] of registeredClasses) {
480
- const objectCommands = await this.getObjectCommandsLazy(
481
- classInfo.name || _key
482
- );
483
- for (const cmd of objectCommands) {
484
- if (!commandNames.has(cmd.name)) {
485
- commandNames.add(cmd.name);
486
- allCommands.push(cmd);
487
- }
488
- }
489
- }
490
- return allCommands;
491
- }
492
- /**
493
- * Generate CRUD commands for a specific object
494
- */
495
- async generateObjectCommands(objectName, _classInfo) {
496
- const commands = [];
497
- const lowerName = objectName.toLowerCase();
498
- const config = ObjectRegistry.getConfig(objectName);
499
- const cliConfig = config.cli;
500
- if (cliConfig === false) return commands;
501
- const excluded = (typeof cliConfig === "object" ? cliConfig.exclude : []) || [];
502
- const included = typeof cliConfig === "object" ? cliConfig.include : null;
503
- const shouldInclude = (command) => {
504
- if (included && !included.includes(command)) return false;
505
- if (excluded.includes(command)) return false;
506
- return true;
507
- };
508
- if (shouldInclude("list")) {
509
- commands.push({
510
- name: `${lowerName}:list`,
511
- description: `List ${objectName} objects`,
512
- aliases: [`${lowerName}:ls`],
513
- options: {
514
- limit: {
515
- type: "string",
516
- description: "limit number of results",
517
- default: "50",
518
- short: "l"
519
- },
520
- offset: {
521
- type: "string",
522
- description: "offset for pagination",
523
- default: "0",
524
- short: "o"
525
- },
526
- "order-by": { type: "string", description: "field to order by" },
527
- where: { type: "string", description: "filter conditions as JSON" },
528
- format: {
529
- type: "string",
530
- description: "output format (table|json)",
531
- default: "table"
532
- }
533
- },
534
- handler: async (_args, options) => {
535
- await this.handleList(objectName, options);
536
- }
537
- });
538
- }
539
- if (shouldInclude("get")) {
540
- commands.push({
541
- name: `${lowerName}:get`,
542
- description: `Get ${objectName} by ID or slug`,
543
- aliases: [`${lowerName}:show`],
544
- args: ["id"],
545
- options: {
546
- format: {
547
- type: "string",
548
- description: "output format (json|yaml)",
549
- default: "json"
550
- }
551
- },
552
- handler: async (args, options) => {
553
- await this.handleGet(objectName, args[0], options);
554
- }
555
- });
556
- }
557
- if (shouldInclude("create")) {
558
- const options = {
559
- interactive: {
560
- type: "boolean",
561
- description: "interactive mode with prompts"
562
- },
563
- "from-file": { type: "string", description: "create from JSON file" }
564
- };
565
- const fields = ObjectRegistry.getFields(objectName);
566
- for (const [fieldName, field] of fields) {
567
- const optionName = fieldName.replace(/_/g, "-");
568
- const description = field.options?.description || `${objectName} ${fieldName}`;
569
- options[optionName] = { type: "string", description };
570
- }
571
- commands.push({
572
- name: `${lowerName}:create`,
573
- description: `Create new ${objectName}`,
574
- aliases: [`${lowerName}:new`],
575
- options,
576
- handler: async (_args, options2) => {
577
- await this.handleCreate(objectName, options2);
578
- }
579
- });
580
- }
581
- if (shouldInclude("update")) {
582
- const options = {
583
- interactive: {
584
- type: "boolean",
585
- description: "interactive mode with prompts"
586
- },
587
- "from-file": { type: "string", description: "update from JSON file" }
588
- };
589
- const fields = ObjectRegistry.getFields(objectName);
590
- for (const [fieldName, field] of fields) {
591
- const optionName = fieldName.replace(/_/g, "-");
592
- const description = field.options?.description || `${objectName} ${fieldName}`;
593
- options[optionName] = { type: "string", description };
594
- }
595
- commands.push({
596
- name: `${lowerName}:update`,
597
- description: `Update ${objectName}`,
598
- aliases: [`${lowerName}:edit`],
599
- args: ["id"],
600
- options,
601
- handler: async (args, options2) => {
602
- await this.handleUpdate(objectName, args[0], options2);
603
- }
604
- });
605
- }
606
- if (shouldInclude("delete")) {
607
- commands.push({
608
- name: `${lowerName}:delete`,
609
- description: `Delete ${objectName}`,
610
- aliases: [`${lowerName}:rm`],
611
- args: ["id"],
612
- options: {
613
- force: { type: "boolean", description: "skip confirmation prompt" }
614
- },
615
- handler: async (args, options) => {
616
- await this.handleDelete(objectName, args[0], options);
617
- }
618
- });
619
- }
620
- const methods = await ObjectRegistry.getAllMethods(objectName);
621
- const crudOperations = ["list", "get", "create", "update", "delete"];
622
- const hasCustomMethodsInInclude = included?.some(
623
- (item) => !crudOperations.includes(item)
624
- );
625
- for (const [methodName, methodDef] of methods) {
626
- const shouldIncludeMethod = () => {
627
- if (!methodDef.isPublic) return false;
628
- if (excluded.includes(methodName)) return false;
629
- if (hasCustomMethodsInInclude && included && !included.includes(methodName)) {
630
- return false;
631
- }
632
- return true;
633
- };
634
- if (!shouldIncludeMethod()) continue;
635
- const methodOptions = {
636
- json: {
637
- type: "boolean",
638
- description: "Output as JSON only (suppress other output)"
639
- }
640
- };
641
- for (const param of methodDef.parameters || []) {
642
- const typeStr = param.type || "";
643
- const isObjectType = this.isObjectTypeParameter(typeStr);
644
- if (isObjectType) {
645
- const match = typeStr.match(/\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/);
646
- if (match) {
647
- const propsStr = match[1];
648
- const propMatches = propsStr.matchAll(/(\w+)(\?)?:\s*([^;]+)/g);
649
- for (const propMatch of propMatches) {
650
- const [, propName, isOptional, propType] = propMatch;
651
- const optionName = propName.replace(/([A-Z])/g, "-$1").toLowerCase();
652
- if (propType.includes("import(")) {
653
- methodOptions[optionName] = {
654
- type: "string",
655
- description: `JSON object${isOptional ? " (optional)" : ""}`
656
- };
657
- } else {
658
- const trimmedType = propType.trim();
659
- methodOptions[optionName] = {
660
- type: trimmedType === "boolean" ? "boolean" : "string",
661
- description: `${trimmedType}${isOptional ? " (optional)" : ""}`
662
- };
663
- }
664
- }
665
- }
666
- } else {
667
- const optionName = param.name.replace(/([A-Z])/g, "-$1").toLowerCase();
668
- const paramType = (param.type || "").trim();
669
- methodOptions[optionName] = {
670
- type: paramType === "boolean" ? "boolean" : "string",
671
- description: `${param.type}${param.optional ? " (optional)" : ""}`,
672
- ...param.default !== void 0 && {
673
- default: String(param.default)
674
- }
675
- };
676
- }
677
- }
678
- const firstParam = (methodDef.parameters || [])[0];
679
- const needsInstance = firstParam?.name === "id";
680
- commands.push({
681
- name: `${lowerName}:${methodName}`,
682
- description: methodDef.description || `Execute ${methodName} on ${objectName}`,
683
- args: needsInstance ? ["id"] : [],
684
- // Only require ID if method has required parameters
685
- options: methodOptions,
686
- handler: async (args, options) => {
687
- if (needsInstance) {
688
- await this.handleCustomMethod(
689
- objectName,
690
- args[0],
691
- methodName,
692
- options
693
- );
694
- } else {
695
- await this.handleSingletonMethod(
696
- objectName,
697
- methodName,
698
- options,
699
- methodDef
700
- );
701
- }
702
- }
703
- });
704
- }
705
- return commands;
706
- }
707
- /**
708
- * Execute a parsed command
709
- */
710
- async executeCommand(parsed, commands, processedArgv = process.argv.slice(2)) {
711
- if (!parsed.command) {
712
- const allCommands = await this.generateAllCommands();
713
- await this.showHelp(allCommands);
714
- return;
715
- }
716
- let command = commands.find(
717
- (cmd) => cmd.name === parsed.command || parsed.command && cmd.aliases && cmd.aliases.includes(parsed.command)
718
- );
719
- if (!command && parsed.command) {
720
- command = await this.findObjectCommand(parsed.command);
721
- }
722
- if (command) {
723
- const requiredArgCount = countRequiredArgs(command.args);
724
- if (parsed.args.length < requiredArgCount) {
725
- const missingArgs = command.args?.slice(parsed.args.length).filter((arg) => !arg.startsWith("[") || !arg.endsWith("]"));
726
- this.exitWithError(
727
- `Missing required arguments: ${missingArgs?.join(", ") || ""}`
728
- );
729
- return;
730
- }
731
- if (!command.handler) {
732
- this.exitWithError(
733
- `Command '${parsed.command}' has no handler defined`
734
- );
735
- return;
736
- }
737
- try {
738
- await command.handler(parsed.args, parsed.options);
739
- return;
740
- } catch (error) {
741
- this.exitWithError(
742
- `Error: ${error instanceof Error ? error.message : "Unknown error"}`
743
- );
744
- return;
745
- }
746
- }
747
- const [
748
- gnodeCommands,
749
- generateCommands,
750
- gitCommands,
751
- initCommands,
752
- utilityCommands,
753
- dispatchCommands,
754
- docsCommands,
755
- playgroundCommands
756
- ] = await Promise.all([
757
- getGnodeCommands(),
758
- getGenerateCommands(),
759
- getGitCommands(),
760
- getInitCommands(),
761
- getUtilityCommands(),
762
- getDispatchCommands(),
763
- getDocsCommands(),
764
- getPlaygroundCommands()
765
- ]);
766
- const builtInCommands = {
767
- ...gnodeCommands,
768
- ...generateCommands,
769
- ...gitCommands,
770
- ...initCommands,
771
- ...utilityCommands,
772
- ...dispatchCommands,
773
- ...docsCommands,
774
- ...playgroundCommands
775
- };
776
- const builtInCommand = builtInCommands[parsed.command] ?? Object.values(builtInCommands).find(
777
- (cmd) => cmd.name === parsed.command || cmd.aliases?.includes(parsed.command ?? "")
778
- );
779
- if (builtInCommand) {
780
- const requiredArgCount = countRequiredArgs(builtInCommand.args);
781
- if (parsed.args.length < requiredArgCount) {
782
- const missingArgs = builtInCommand.args?.slice(parsed.args.length).filter((arg) => !arg.startsWith("[") || !arg.endsWith("]"));
783
- this.exitWithError(
784
- `Missing required arguments: ${missingArgs?.join(", ") || ""}`
785
- );
786
- return;
787
- }
788
- if (!builtInCommand.handler) {
789
- this.exitWithError(
790
- `Command '${parsed.command}' has no handler defined`
791
- );
792
- return;
793
- }
794
- const reParsed = parseCliArgs(processedArgv, [builtInCommand], {});
795
- try {
796
- await builtInCommand.handler(reParsed.args, reParsed.options);
797
- return;
798
- } catch (error) {
799
- this.exitWithError(
800
- `Error: ${error instanceof Error ? error.message : "Unknown error"}`
801
- );
802
- return;
803
- }
804
- }
805
- await this.ensureManifestLoaded();
806
- const registeredClasses = ObjectRegistry.getAllClasses();
807
- let matchingObject;
808
- for (const [_key, info] of registeredClasses) {
809
- if ((info.name || _key).toLowerCase() === parsed.command?.toLowerCase()) {
810
- matchingObject = info.name || _key;
811
- break;
812
- }
813
- }
814
- if (matchingObject) {
815
- const objectCommands = await this.getObjectCommandsLazy(matchingObject);
816
- await this.showObjectHelp(matchingObject, objectCommands);
817
- return;
818
- }
819
- this.exitWithError(`Unknown command '${parsed.command}'`);
820
- }
821
- /**
822
- * Show help for a specific object and its available commands
823
- */
824
- async showObjectHelp(objectName, objectCommands) {
825
- const classInfo = ObjectRegistry.getClass(objectName);
826
- const lowerName = objectName.toLowerCase();
827
- console.log(`
828
- ${objectName}`);
829
- console.log("=".repeat(objectName.length));
830
- if (classInfo?.packageName) {
831
- console.log(`Package: ${classInfo.packageName}`);
832
- }
833
- if (objectCommands.length === 0) {
834
- console.log("\nNo CLI commands available for this object.");
835
- console.log(
836
- "Check that cli: true or cli: { include: [...] } is set in @smrt() decorator."
837
- );
838
- return;
839
- }
840
- console.log("\nAvailable commands:");
841
- for (const cmd of objectCommands) {
842
- const cmdName = cmd.name.replace(`${lowerName}:`, "");
843
- const args = cmd.args ? ` ${cmd.args.map((arg) => `<${arg}>`).join(" ")}` : "";
844
- console.log(` smrt ${lowerName} ${cmdName}${args}`);
845
- console.log(` ${cmd.description}`);
846
- if (cmd.options && Object.keys(cmd.options).length > 0) {
847
- for (const [optName, opt] of Object.entries(cmd.options)) {
848
- const short = opt.short ? `-${opt.short}, ` : "";
849
- const def = opt.default ? ` (default: ${opt.default})` : "";
850
- console.log(` ${short}--${optName}${def}`);
851
- }
852
- }
853
- console.log();
854
- }
855
- const fields = ObjectRegistry.getFields(objectName);
856
- if (fields.size > 0) {
857
- console.log("Fields:");
858
- for (const [fieldName, field] of fields) {
859
- const required = field.options?.required ? " (required)" : "";
860
- console.log(` ${fieldName}: ${field.type}${required}`);
861
- }
862
- }
863
- }
864
- /**
865
- * Generate utility commands
866
- */
867
- generateUtilityCommands() {
868
- const commands = [];
869
- commands.push({
870
- name: "objects",
871
- description: "List all registered smrt objects",
872
- aliases: ["ls"],
873
- options: {
874
- verbose: {
875
- type: "boolean",
876
- description: "Show detailed output including methods",
877
- short: "v"
878
- },
879
- json: {
880
- type: "boolean",
881
- description: "Output as JSON"
882
- }
883
- },
884
- handler: async (_args, options) => {
885
- const registeredClasses = ObjectRegistry.getAllClasses();
886
- if (registeredClasses.size === 0) {
887
- console.log("No SMRT objects found.");
888
- console.log("\nTo discover objects:");
889
- console.log(" • Build your project: npm run build");
890
- console.log(" • Ensure .smrt/register.js exists");
891
- console.log(" • Run: smrt introspect for details");
892
- return;
893
- }
894
- if (options.json) {
895
- const output = {};
896
- for (const [key, classInfo] of registeredClasses) {
897
- const displayName = classInfo.name || key;
898
- const config = ObjectRegistry.getConfig(key);
899
- const fields = ObjectRegistry.getFields(key);
900
- const methods = await ObjectRegistry.getAllMethods(key);
901
- output[classInfo.qualifiedName || displayName] = {
902
- name: displayName,
903
- package: classInfo.packageName || "project",
904
- hasConstructor: !!classInfo.constructor,
905
- hasCollection: !!classInfo.collectionConstructor,
906
- config,
907
- fields: Object.fromEntries(fields),
908
- methods: Object.fromEntries(methods)
909
- };
910
- }
911
- console.log(JSON.stringify(output, null, 2));
912
- return;
913
- }
914
- console.log("Registered SMRT objects:\n");
915
- const byPackage = /* @__PURE__ */ new Map();
916
- for (const [key, classInfo] of registeredClasses) {
917
- const pkg = classInfo.packageName || "project";
918
- if (!byPackage.has(pkg)) {
919
- byPackage.set(pkg, []);
920
- }
921
- byPackage.get(pkg)?.push({ display: classInfo.name || key, key });
922
- }
923
- for (const [pkg, entries] of byPackage) {
924
- console.log(` ${pkg}:`);
925
- for (const { display: objName, key: objKey } of entries) {
926
- const config = ObjectRegistry.getConfig(objKey);
927
- const cliConfig = config.cli;
928
- let cliMethods = [];
929
- if (cliConfig) {
930
- const methods = await ObjectRegistry.getAllMethods(objKey);
931
- const methodNames = Array.from(methods.keys());
932
- if (typeof cliConfig === "object" && cliConfig.include) {
933
- cliMethods = methodNames.filter(
934
- (m) => cliConfig.include?.includes(m) && methods.get(m)?.isPublic
935
- );
936
- } else if (cliConfig === true) {
937
- cliMethods = methodNames.filter(
938
- (m) => methods.get(m)?.isPublic
939
- );
940
- }
941
- }
942
- if (options.verbose) {
943
- console.log(` • ${objName}`);
944
- if (cliMethods.length > 0) {
945
- console.log(` CLI: ${cliMethods.join(", ")}`);
946
- }
947
- const fields = ObjectRegistry.getFields(objKey);
948
- const fieldNames = Array.from(fields.keys()).slice(0, 5);
949
- if (fieldNames.length > 0) {
950
- console.log(
951
- ` Fields: ${fieldNames.join(", ")}${fields.size > 5 ? "..." : ""}`
952
- );
953
- }
954
- } else {
955
- const methodStr = cliMethods.length > 0 ? ` → ${cliMethods.join(", ")}` : "";
956
- console.log(` • ${objName}${methodStr}`);
957
- }
958
- }
959
- console.log();
960
- }
961
- console.log(
962
- `Total: ${registeredClasses.size} objects from ${byPackage.size} source(s)`
963
- );
964
- }
965
- });
966
- commands.push({
967
- name: "schema",
968
- description: "Show schema for an object",
969
- args: ["object"],
970
- handler: this.createSchemaHandler()
971
- });
972
- commands.push({
973
- name: "help",
974
- description: "Show help information",
975
- aliases: ["h"],
976
- handler: async (_args, _options) => {
977
- await this.showHelp(commands);
978
- }
979
- });
980
- commands.push({
981
- name: "version",
982
- description: "Show version information",
983
- aliases: ["v"],
984
- handler: async (_args, _options) => {
985
- console.log(`${this.config.name} v${this.config.version}`);
986
- }
987
- });
988
- commands.push({
989
- name: "status",
990
- description: "Show system status",
991
- handler: async (_args, _options) => {
992
- console.log("System Status:");
993
- console.log(`- CLI: ${this.config.name} v${this.config.version}`);
994
- console.log(
995
- `- Database: ${this.context.db ? "Connected" : "Not connected"}`
996
- );
997
- console.log(`- AI: ${this.context.ai ? "Available" : "Not available"}`);
998
- console.log(`- User: ${this.context.user?.id || "Not authenticated"}`);
999
- }
1000
- });
1001
- return commands;
1002
- }
1003
- /**
1004
- * Create schema command handler
1005
- */
1006
- createSchemaHandler() {
1007
- return async (args, _options) => {
1008
- const objectName = args[0];
1009
- const fields = ObjectRegistry.getFields(objectName);
1010
- if (fields.size === 0) {
1011
- this.exitWithError(`Object ${objectName} not found`);
1012
- return;
1013
- }
1014
- console.log(`Schema for ${objectName}:`);
1015
- for (const [fieldName, field] of fields) {
1016
- console.log(
1017
- ` ${fieldName}: ${field.type}${field.options?.required ? " (required)" : ""}`
1018
- );
1019
- if (field.options?.description) {
1020
- console.log(` ${field.options.description}`);
1021
- }
1022
- }
1023
- };
1024
- }
1025
- /**
1026
- * Show help information
1027
- */
1028
- async showHelp(commands) {
1029
- console.log(`${this.config.name} v${this.config.version}`);
1030
- console.log(this.config.description);
1031
- console.log();
1032
- const [
1033
- gnodeCommands,
1034
- generateCommands,
1035
- gitCommands,
1036
- initCommands,
1037
- utilityCommands,
1038
- dispatchCommands,
1039
- docsCommands,
1040
- playgroundCommands
1041
- ] = await Promise.all([
1042
- getGnodeCommands(),
1043
- getGenerateCommands(),
1044
- getGitCommands(),
1045
- getInitCommands(),
1046
- getUtilityCommands(),
1047
- getDispatchCommands(),
1048
- getDocsCommands(),
1049
- getPlaygroundCommands()
1050
- ]);
1051
- console.log("Project Setup:");
1052
- for (const command of Object.values(initCommands)) {
1053
- this.showCommandHelp(command);
1054
- }
1055
- console.log();
1056
- console.log("Playground:");
1057
- for (const command of Object.values(playgroundCommands)) {
1058
- this.showCommandHelp(command);
1059
- }
1060
- console.log();
1061
- console.log("Utility Commands:");
1062
- for (const command of Object.values(utilityCommands)) {
1063
- this.showCommandHelp(command);
1064
- }
1065
- console.log();
1066
- console.log("Dispatch (Inter-Agent Communication):");
1067
- for (const command of Object.values(dispatchCommands)) {
1068
- this.showCommandHelp(command);
1069
- }
1070
- console.log();
1071
- console.log("Git Integration:");
1072
- for (const command of Object.values(gitCommands)) {
1073
- this.showCommandHelp(command);
1074
- }
1075
- console.log();
1076
- console.log("Gnode Commands:");
1077
- for (const command of Object.values(gnodeCommands)) {
1078
- this.showCommandHelp(command);
1079
- }
1080
- console.log("Code Generation:");
1081
- for (const command of Object.values(generateCommands)) {
1082
- this.showCommandHelp(command);
1083
- }
1084
- console.log("Documentation:");
1085
- for (const command of Object.values(docsCommands)) {
1086
- this.showCommandHelp(command);
1087
- }
1088
- console.log();
1089
- const builtInUtilityCommands = commands.filter(
1090
- (cmd) => cmd.name === "objects" || cmd.name === "schema" || cmd.name === "help" || cmd.name === "version" || cmd.name === "status"
1091
- );
1092
- if (builtInUtilityCommands.length > 0) {
1093
- console.log("Object Utilities:");
1094
- for (const command of builtInUtilityCommands) {
1095
- this.showCommandHelp(command);
1096
- }
1097
- }
1098
- const objectCommands = commands.filter(
1099
- (cmd) => !builtInUtilityCommands.includes(cmd)
1100
- );
1101
- if (objectCommands.length > 0) {
1102
- console.log("Object Commands (auto-generated):");
1103
- for (const command of objectCommands) {
1104
- this.showCommandHelp(command);
1105
- }
1106
- }
1107
- }
1108
- /**
1109
- * Show help for a single command
1110
- */
1111
- showCommandHelp(command) {
1112
- const aliases = command.aliases ? ` (${command.aliases.join(", ")})` : "";
1113
- const args = command.args ? ` ${command.args.map((arg) => `<${arg}>`).join(" ")}` : "";
1114
- console.log(` ${command.name}${args}${aliases}`);
1115
- console.log(` ${command.description}`);
1116
- if (command.options) {
1117
- for (const [name, option] of Object.entries(command.options)) {
1118
- const short = option.short ? `-${option.short}, ` : "";
1119
- console.log(` ${short}--${name}: ${option.description}`);
1120
- }
1121
- }
1122
- console.log();
1123
- }
1124
- /**
1125
- * Create a simple spinner
1126
- */
1127
- createSpinner(text) {
1128
- const isTTY = process.stdout.isTTY && typeof process.stdout.clearLine === "function" && typeof process.stdout.cursorTo === "function";
1129
- if (this.config.colors && isTTY) {
1130
- process.stdout.write(`⠋ ${text}`);
1131
- return {
1132
- succeed: (successText) => {
1133
- process.stdout.clearLine(0);
1134
- process.stdout.cursorTo(0);
1135
- console.log(`✅ ${successText || text}`);
1136
- },
1137
- fail: (errorText) => {
1138
- process.stdout.clearLine(0);
1139
- process.stdout.cursorTo(0);
1140
- console.log(`❌ ${errorText || text}`);
1141
- }
1142
- };
1143
- }
1144
- console.log(text);
1145
- return {
1146
- succeed: (successText) => console.log(successText || "Done"),
1147
- fail: (errorText) => console.log(errorText || "Failed")
1148
- };
1149
- }
1150
- /**
1151
- * Prompt for input
1152
- */
1153
- async prompt(message) {
1154
- const rl = createInterface({
1155
- input: process.stdin,
1156
- output: process.stdout
1157
- });
1158
- return new Promise((resolve) => {
1159
- rl.question(`${message} `, (answer) => {
1160
- rl.close();
1161
- resolve(answer);
1162
- });
1163
- });
1164
- }
1165
- /**
1166
- * Confirm prompt
1167
- */
1168
- async confirm(message) {
1169
- const answer = await this.prompt(`${message} (y/n)`);
1170
- return answer.toLowerCase().startsWith("y");
1171
- }
1172
- /**
1173
- * Handle LIST command
1174
- */
1175
- async handleList(objectName, options) {
1176
- const spinner = this.createSpinner(`Listing ${objectName} objects...`);
1177
- try {
1178
- const collection = await this.getCollection(objectName);
1179
- const listOptions = {
1180
- limit: Number.parseInt(String(options.limit), 10),
1181
- offset: Number.parseInt(String(options.offset), 10)
1182
- };
1183
- const orderBy = options["order-by"] ?? options.orderBy;
1184
- if (typeof orderBy === "string" && orderBy) {
1185
- listOptions.orderBy = orderBy;
1186
- }
1187
- if (typeof options.where === "string" && options.where) {
1188
- listOptions.where = JSON.parse(options.where);
1189
- }
1190
- const results = await collection.list(listOptions);
1191
- spinner.succeed(`Found ${results.length} ${objectName} objects`);
1192
- if (options.format === "json") {
1193
- console.log(JSON.stringify(results, null, 2));
1194
- } else {
1195
- this.displayTable(results, objectName);
1196
- }
1197
- } catch (error) {
1198
- spinner.fail(`Failed to list ${objectName} objects`);
1199
- this.exitWithError(
1200
- error instanceof Error ? error.message : "Unknown error"
1201
- );
1202
- }
1203
- }
1204
- /**
1205
- * Handle GET command
1206
- */
1207
- async handleGet(objectName, id, options) {
1208
- const spinner = this.createSpinner(`Getting ${objectName}...`);
1209
- try {
1210
- const collection = await this.getCollection(objectName);
1211
- const result = await collection.get(id);
1212
- if (!result) {
1213
- spinner.fail(`${objectName} not found`);
1214
- this.exitWithError(`${objectName} not found`);
1215
- return;
1216
- }
1217
- spinner.succeed(`Found ${objectName}`);
1218
- if (options.format === "yaml") {
1219
- console.log(this.toYamlString(result));
1220
- } else {
1221
- console.log(JSON.stringify(result, null, 2));
1222
- }
1223
- } catch (error) {
1224
- spinner.fail(`Failed to get ${objectName}`);
1225
- this.exitWithError(
1226
- error instanceof Error ? error.message : "Unknown error"
1227
- );
1228
- }
1229
- }
1230
- /**
1231
- * Handle CREATE command
1232
- */
1233
- async handleCreate(objectName, options) {
1234
- try {
1235
- let data = {};
1236
- const fromFile = options["from-file"] ?? options.fromFile;
1237
- if (typeof fromFile === "string" && fromFile) {
1238
- const fs = await import("node:fs/promises");
1239
- const content = await fs.readFile(fromFile, "utf-8");
1240
- data = JSON.parse(content);
1241
- } else if (options.interactive && this.config.prompt) {
1242
- data = await this.promptForFields(objectName, {});
1243
- } else {
1244
- const fields = ObjectRegistry.getFields(objectName);
1245
- for (const [fieldName] of fields) {
1246
- const optionName = fieldName.replace(/_/g, "-");
1247
- if (options[optionName] !== void 0) {
1248
- data[fieldName] = this.parseFieldValue(String(options[optionName]));
1249
- }
1250
- }
1251
- }
1252
- const spinner = this.createSpinner(`Creating ${objectName}...`);
1253
- const collection = await this.getCollection(objectName);
1254
- const result = await collection.create(data);
1255
- await result.save();
1256
- spinner.succeed(`Created ${objectName} with ID: ${result.id}`);
1257
- if (!options.quiet) {
1258
- console.log(JSON.stringify(result, null, 2));
1259
- }
1260
- } catch (error) {
1261
- this.exitWithError(
1262
- error instanceof Error ? error.message : "Unknown error"
1263
- );
1264
- }
1265
- }
1266
- /**
1267
- * Handle UPDATE command
1268
- */
1269
- async handleUpdate(objectName, id, options) {
1270
- try {
1271
- const collection = await this.getCollection(objectName);
1272
- const existing = await collection.get(id);
1273
- if (!existing) {
1274
- this.exitWithError(`${objectName} not found`);
1275
- return;
1276
- }
1277
- let data = {};
1278
- const fromFile = options["from-file"] ?? options.fromFile;
1279
- if (typeof fromFile === "string" && fromFile) {
1280
- const fs = await import("node:fs/promises");
1281
- const content = await fs.readFile(fromFile, "utf-8");
1282
- data = JSON.parse(content);
1283
- } else if (options.interactive && this.config.prompt) {
1284
- data = await this.promptForFields(
1285
- objectName,
1286
- existing
1287
- );
1288
- } else {
1289
- const fields = ObjectRegistry.getFields(objectName);
1290
- for (const [fieldName] of fields) {
1291
- const optionName = fieldName.replace(/_/g, "-");
1292
- if (options[optionName] !== void 0) {
1293
- data[fieldName] = this.parseFieldValue(String(options[optionName]));
1294
- }
1295
- }
1296
- }
1297
- const spinner = this.createSpinner(`Updating ${objectName}...`);
1298
- Object.assign(existing, data);
1299
- await existing.save();
1300
- spinner.succeed(`Updated ${objectName}`);
1301
- if (!options.quiet) {
1302
- console.log(JSON.stringify(existing, null, 2));
1303
- }
1304
- } catch (error) {
1305
- this.exitWithError(
1306
- error instanceof Error ? error.message : "Unknown error"
1307
- );
1308
- }
1309
- }
1310
- /**
1311
- * Handle DELETE command
1312
- */
1313
- async handleDelete(objectName, id, options) {
1314
- try {
1315
- const collection = await this.getCollection(objectName);
1316
- const existing = await collection.get(id);
1317
- if (!existing) {
1318
- this.exitWithError(`${objectName} not found`);
1319
- return;
1320
- }
1321
- if (!options.force && this.config.prompt) {
1322
- const label = existing.name || existing.slug || existing.id;
1323
- const confirmed = await this.confirm(
1324
- `Are you sure you want to delete ${objectName} "${label}"?`
1325
- );
1326
- if (!confirmed) {
1327
- console.log("Cancelled");
1328
- return;
1329
- }
1330
- }
1331
- const spinner = this.createSpinner(`Deleting ${objectName}...`);
1332
- await existing.delete();
1333
- spinner.succeed(`Deleted ${objectName}`);
1334
- } catch (error) {
1335
- this.exitWithError(
1336
- error instanceof Error ? error.message : "Unknown error"
1337
- );
1338
- }
1339
- }
1340
- /**
1341
- * Handle custom method execution
1342
- */
1343
- async handleCustomMethod(objectName, id, methodName, options) {
1344
- try {
1345
- const collection = await this.getCollection(objectName);
1346
- const obj = await collection.get(id);
1347
- if (!obj) {
1348
- this.exitWithError(`${objectName} not found`);
1349
- return;
1350
- }
1351
- const methods = await ObjectRegistry.getAllMethods(objectName);
1352
- const methodDef = methods.get(methodName);
1353
- if (!methodDef) {
1354
- this.exitWithError(`Method ${methodName} not found on ${objectName}`);
1355
- return;
1356
- }
1357
- const jsonMode = options.json === true;
1358
- const spinner = jsonMode ? {
1359
- succeed: (_text) => {
1360
- },
1361
- fail: (msg) => {
1362
- if (msg) {
1363
- console.error(msg);
1364
- }
1365
- }
1366
- } : this.createSpinner(`Executing ${methodName} on ${objectName}...`);
1367
- const methodParams = methodDef.parameters || [];
1368
- const methodCallArgs = [];
1369
- for (const param of methodParams) {
1370
- const typeStr = param.type || "";
1371
- const isObjectType = this.isObjectTypeParameter(typeStr);
1372
- if (isObjectType) {
1373
- const objArg = {};
1374
- const match = typeStr.match(/\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/);
1375
- if (match) {
1376
- const propsStr = match[1];
1377
- const propMatches = propsStr.matchAll(/(\w+)(\?)?:\s*([^;]+)/g);
1378
- for (const propMatch of propMatches) {
1379
- const [, propName] = propMatch;
1380
- const optionName = propName.replace(/([A-Z])/g, "-$1").toLowerCase();
1381
- if (options[optionName] !== void 0) {
1382
- let value = options[optionName];
1383
- if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) {
1384
- try {
1385
- value = JSON.parse(value);
1386
- } catch {
1387
- }
1388
- }
1389
- objArg[propName] = value;
1390
- }
1391
- }
1392
- }
1393
- if (Object.keys(objArg).length > 0) {
1394
- methodCallArgs.push(objArg);
1395
- } else if (!param.optional) {
1396
- methodCallArgs.push({});
1397
- } else {
1398
- methodCallArgs.push(void 0);
1399
- }
1400
- } else {
1401
- const optionName = param.name.replace(/([A-Z])/g, "-$1").toLowerCase();
1402
- if (options[optionName] !== void 0) {
1403
- methodCallArgs.push(options[optionName]);
1404
- } else if (param.default !== void 0) {
1405
- methodCallArgs.push(param.default);
1406
- } else {
1407
- methodCallArgs.push(void 0);
1408
- }
1409
- }
1410
- }
1411
- const method = obj[methodName];
1412
- if (typeof method !== "function") {
1413
- spinner.fail(`Method ${methodName} is not a function`);
1414
- this.exitWithError(`Method ${methodName} is not callable`);
1415
- return;
1416
- }
1417
- const result = await method.call(
1418
- obj,
1419
- ...methodCallArgs
1420
- );
1421
- spinner.succeed(`Executed ${methodName}`);
1422
- console.log(JSON.stringify(result, null, 2));
1423
- } catch (error) {
1424
- this.exitWithError(
1425
- error instanceof Error ? error.message : "Unknown error"
1426
- );
1427
- }
1428
- }
1429
- /**
1430
- * Handle singleton method (no parameters, no database lookup)
1431
- * Creates a new instance with proper config and calls the method
1432
- */
1433
- async handleSingletonMethod(objectName, methodName, options, methodDef) {
1434
- try {
1435
- const classInfo = ObjectRegistry.getClass(objectName);
1436
- if (!classInfo || !classInfo.constructor) {
1437
- const availableObjects = Array.from(
1438
- ObjectRegistry.getAllClasses().values()
1439
- ).map((info) => info.name);
1440
- const availableList = availableObjects.length > 0 ? `Available objects:
1441
- ${availableObjects.join("\n ")}` : 'No objects registered. Run "npm run build" to generate registrations.';
1442
- this.exitWithError(
1443
- `Object class '${objectName}' not found.
1444
-
1445
- ${availableList}
1446
-
1447
- Troubleshooting:
1448
- 1. Run "npm run build" to generate .smrt/register.js
1449
- 2. Ensure package exports classes correctly
1450
- 3. Run "smrt doctor" for diagnostics`
1451
- );
1452
- return;
1453
- }
1454
- const jsonMode = options.json === true;
1455
- const spinner = jsonMode ? {
1456
- succeed: (_text) => {
1457
- },
1458
- fail: (msg) => {
1459
- if (msg) {
1460
- console.error(msg);
1461
- }
1462
- }
1463
- } : this.createSpinner(`Executing ${methodName} on ${objectName}...`);
1464
- const { getConfig, getPackageConfig, getModuleConfig } = await import("@happyvertical/smrt-config");
1465
- const smrtConfig = getConfig() || {};
1466
- const moduleConfig = getModuleConfig(objectName.toLowerCase(), {});
1467
- const { DEFAULT_CLI_CONFIG } = await import("./config-C8pQD-tk.js");
1468
- const cliConfig = getPackageConfig("cli", DEFAULT_CLI_CONFIG);
1469
- let db = this.context.db;
1470
- if (!db && cliConfig?.database?.url) {
1471
- const { getDatabase } = await import("@happyvertical/sql");
1472
- db = await getDatabase({
1473
- type: cliConfig.database.type || "sqlite",
1474
- url: cliConfig.database.url
1475
- });
1476
- }
1477
- const isManifestStub = classInfo.constructor?._isManifestStub === true;
1478
- if (process.env.DEBUG) {
1479
- console.log(`[DEBUG] ${objectName} constructor info:`);
1480
- console.log(` - isManifestStub: ${isManifestStub}`);
1481
- console.log(
1482
- ` - constructor name: ${classInfo.constructor?.name || "undefined"}`
1483
- );
1484
- console.log(` - packageName: ${classInfo.packageName || "local"}`);
1485
- }
1486
- if (isManifestStub) {
1487
- this.exitWithError(
1488
- `${objectName} is registered from manifest but the real class wasn't loaded.
1489
-
1490
- This usually means:
1491
- 1. The .smrt/register.js file doesn't import the class
1492
- 2. The package doesn't export the class properly
1493
- 3. The class name in the package doesn't match the manifest
1494
-
1495
- Try:
1496
- - Run 'npm run build' to regenerate .smrt/register.js
1497
- - Check that the package exports the ${objectName} class
1498
- - Check .smrt/register.js imports match the package exports`
1499
- );
1500
- return;
1501
- }
1502
- if (typeof classInfo.constructor !== "function") {
1503
- this.exitWithError(
1504
- `${objectName} constructor is not available.
1505
- This usually means the class wasn't properly exported or registered.
1506
- Check that the package exports the class and .smrt/register.js imports it.`
1507
- );
1508
- return;
1509
- }
1510
- const useCliDb = db && !moduleConfig?.db;
1511
- const instanceConfig = {
1512
- ...smrtConfig,
1513
- ...moduleConfig,
1514
- ...useCliDb ? { db } : {},
1515
- ...this.context.ai ? { ai: this.context.ai } : {},
1516
- // In JSON mode, silence all log output to ensure clean JSON
1517
- ...jsonMode && { silent: true }
1518
- };
1519
- const obj = new classInfo.constructor(instanceConfig);
1520
- if (typeof obj.initialize === "function") {
1521
- await obj.initialize();
1522
- }
1523
- const method = obj[methodName];
1524
- if (typeof method !== "function") {
1525
- spinner.fail(`Method ${methodName} is not a function`);
1526
- this.exitWithError(`Method ${methodName} is not callable`);
1527
- return;
1528
- }
1529
- const methodParams = methodDef?.parameters || [];
1530
- const methodCallArgs = [];
1531
- for (const param of methodParams) {
1532
- const typeStr = param.type || "";
1533
- const isObjectType = this.isObjectTypeParameter(typeStr);
1534
- if (isObjectType) {
1535
- const objArg = {};
1536
- const match = typeStr.match(/\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/);
1537
- if (match) {
1538
- const propsStr = match[1];
1539
- const propMatches = propsStr.matchAll(/(\w+)(\?)?:\s*([^;]+)/g);
1540
- for (const propMatch of propMatches) {
1541
- const [, propName] = propMatch;
1542
- const optionName = propName.replace(/([A-Z])/g, "-$1").toLowerCase();
1543
- if (options[optionName] !== void 0) {
1544
- let value = options[optionName];
1545
- if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) {
1546
- try {
1547
- value = JSON.parse(value);
1548
- } catch {
1549
- }
1550
- }
1551
- objArg[propName] = value;
1552
- }
1553
- }
1554
- }
1555
- if (Object.keys(objArg).length > 0) {
1556
- methodCallArgs.push(objArg);
1557
- } else if (!param.optional) {
1558
- methodCallArgs.push({});
1559
- } else {
1560
- methodCallArgs.push(void 0);
1561
- }
1562
- } else {
1563
- const optionName = param.name.replace(/([A-Z])/g, "-$1").toLowerCase();
1564
- if (options[optionName] !== void 0) {
1565
- methodCallArgs.push(options[optionName]);
1566
- } else if (param.default !== void 0) {
1567
- methodCallArgs.push(param.default);
1568
- } else {
1569
- methodCallArgs.push(void 0);
1570
- }
1571
- }
1572
- }
1573
- const result = await method.call(
1574
- obj,
1575
- ...methodCallArgs
1576
- );
1577
- spinner.succeed(`Executed ${methodName}`);
1578
- if (result !== void 0) {
1579
- console.log(JSON.stringify(result, null, 2));
1580
- }
1581
- } catch (error) {
1582
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
1583
- const errorStack = error instanceof Error ? error.stack : void 0;
1584
- console.error(`
1585
- Error executing ${objectName}.${methodName}():`);
1586
- console.error(` ${errorMessage}`);
1587
- if (errorStack && process.env.DEBUG) {
1588
- console.error("\nStack trace:");
1589
- console.error(errorStack);
1590
- }
1591
- const schemaGuidance = errorMessage.includes("Run 'smrt db:migrate'");
1592
- console.error(
1593
- schemaGuidance ? "\nTip: Prepare the database schema before running generated CLI commands." : "\nTip: Set DEBUG=1 for full stack trace, or check the method implementation."
1594
- );
1595
- this.exitWithError(errorMessage);
1596
- }
1597
- }
1598
- /**
1599
- * Get or create collection for an object
1600
- * Uses database configuration from smrt.config.js or defaults to :memory:
1601
- */
1602
- async getCollection(objectName) {
1603
- if (!this.collections.has(objectName)) {
1604
- const classInfo = ObjectRegistry.getClass(objectName);
1605
- if (!classInfo || !classInfo.collectionConstructor) {
1606
- const availableObjects = Array.from(
1607
- ObjectRegistry.getAllClasses().values()
1608
- ).map((info) => info.name);
1609
- throw new Error(
1610
- `Object '${objectName}' not found or has no collection constructor.
1611
-
1612
- Available objects:
1613
- ${availableObjects.join("\n ")}
1614
-
1615
- Troubleshooting:
1616
- 1. If from external package, ensure it's installed:
1617
- npm install <package-name>
1618
-
1619
- 2. Rebuild your project to regenerate manifest:
1620
- npm run build
1621
-
1622
- 3. Check .smrt/manifest.json contains the object
1623
-
1624
- 4. Verify package exports classes correctly
1625
- 5. Run with verbose mode for more details:
1626
- SMRT_CLI_VERBOSE=true npx smrt ${objectName}:list
1627
- `
1628
- );
1629
- }
1630
- let db = this.context.db;
1631
- if (!db) {
1632
- const { getPackageConfig } = await import("@happyvertical/smrt-config");
1633
- const { DEFAULT_CLI_CONFIG } = await import("./config-C8pQD-tk.js");
1634
- const config = getPackageConfig("cli", DEFAULT_CLI_CONFIG);
1635
- const { getDatabase } = await import("@happyvertical/sql");
1636
- db = await getDatabase({
1637
- type: config.database.type,
1638
- url: config.database.url
1639
- });
1640
- if (config.verbose) {
1641
- console.log(`[CLI] Using database: ${config.database.url}`);
1642
- }
1643
- }
1644
- const collection2 = new classInfo.collectionConstructor({
1645
- ai: this.context.ai,
1646
- db
1647
- });
1648
- await collection2.initialize();
1649
- this.collections.set(objectName, collection2);
1650
- }
1651
- const collection = this.collections.get(objectName);
1652
- if (!collection) throw new Error(`Collection ${objectName} not found`);
1653
- return collection;
1654
- }
1655
- /**
1656
- * Interactive field prompts
1657
- */
1658
- async promptForFields(objectName, current) {
1659
- const fields = ObjectRegistry.getFields(objectName);
1660
- const result = {};
1661
- for (const [fieldName, field] of fields) {
1662
- const currentValue = current[fieldName];
1663
- let message = `${fieldName}`;
1664
- if (field.options?.description) {
1665
- message += ` (${field.options.description})`;
1666
- }
1667
- if (currentValue !== void 0) {
1668
- message += ` [${currentValue}]`;
1669
- }
1670
- message += ": ";
1671
- if (field.type === "boolean") {
1672
- result[fieldName] = await this.confirm(message);
1673
- } else {
1674
- const input = await this.prompt(message);
1675
- if (input.trim()) {
1676
- result[fieldName] = this.parseFieldValue(input);
1677
- } else if (currentValue !== void 0) {
1678
- result[fieldName] = currentValue;
1679
- }
1680
- }
1681
- }
1682
- return result;
1683
- }
1684
- /**
1685
- * Parse field value from string
1686
- */
1687
- parseFieldValue(value) {
1688
- try {
1689
- return JSON.parse(value);
1690
- } catch {
1691
- return value;
1692
- }
1693
- }
1694
- /**
1695
- * Display results as table
1696
- */
1697
- displayTable(results, objectName) {
1698
- if (results.length === 0) {
1699
- console.log(`No ${objectName} objects found`);
1700
- return;
1701
- }
1702
- const keys = ["id", "name", "slug", "created_at"];
1703
- const rows = results.map((item) => {
1704
- const record = item;
1705
- return keys.map((key) => String(record[key] || "").substring(0, 30));
1706
- });
1707
- console.log();
1708
- console.log(keys.join(" "));
1709
- console.log("-".repeat(80));
1710
- rows.forEach((row) => {
1711
- console.log(row.join(" "));
1712
- });
1713
- console.log();
1714
- }
1715
- /**
1716
- * Convert object to YAML-like string
1717
- */
1718
- toYamlString(obj, indent = 0) {
1719
- const spaces = " ".repeat(indent);
1720
- let result = "";
1721
- for (const [key, value] of Object.entries(obj)) {
1722
- if (value === null || value === void 0) {
1723
- result += `${spaces}${key}: null
1724
- `;
1725
- } else if (typeof value === "object" && !Array.isArray(value)) {
1726
- result += `${spaces}${key}:
1727
- ${this.toYamlString(value, indent + 1)}`;
1728
- } else if (Array.isArray(value)) {
1729
- result += `${spaces}${key}:
1730
- `;
1731
- value.forEach((item) => {
1732
- result += `${spaces} - ${item}
1733
- `;
1734
- });
1735
- } else {
1736
- result += `${spaces}${key}: ${value}
1737
- `;
1738
- }
1739
- }
1740
- return result;
1741
- }
87
+ if (!_playgroundCommands) {
88
+ const { playgroundCommands } = await import("./commands-gnJG0EIL.js");
89
+ _playgroundCommands = playgroundCommands;
90
+ }
91
+ return _playgroundCommands;
1742
92
  }
93
+ /**
94
+ * Generate CLI commands for smrt objects
95
+ */
96
+ var CLIGenerator = class {
97
+ config;
98
+ context;
99
+ collections = /* @__PURE__ */ new Map();
100
+ commandCache = null;
101
+ /** Lazy-loaded cache for object commands (key: objectName lowercase) */
102
+ objectCommandsCache = /* @__PURE__ */ new Map();
103
+ /** Set of registered object names (lowercase) for quick lookup */
104
+ registeredObjectNames = null;
105
+ /** Whether manifest/classes have been loaded */
106
+ manifestLoaded = false;
107
+ constructor(config = {}, context = {}) {
108
+ this.config = {
109
+ name: "smrt",
110
+ version: CLI_VERSION,
111
+ description: "Admin CLI for smrt objects",
112
+ prompt: true,
113
+ colors: true,
114
+ ...config
115
+ };
116
+ this.context = context;
117
+ }
118
+ /**
119
+ * Check if running in test environment
120
+ */
121
+ isTestMode() {
122
+ const testGlobals = global;
123
+ return process.env.NODE_ENV === "test" || process.env.VITEST === "true" || typeof testGlobals.it === "function" || typeof testGlobals.describe === "function";
124
+ }
125
+ /**
126
+ * Check if a type string represents an inline object type parameter
127
+ * e.g., "{ meetingId?: string; limit?: number }"
128
+ *
129
+ * Note: Only supports single-level nested braces. Deeply nested types
130
+ * like "{ config: { nested: { deep: string } } }" are not fully supported.
131
+ */
132
+ isObjectTypeParameter(typeStr) {
133
+ return typeStr.includes("{") && typeStr.includes("}") && typeStr.includes(":");
134
+ }
135
+ /**
136
+ * Try to load user's compiled classes for runtime execution
137
+ *
138
+ * Loads both local classes (from project entry point) and external classes
139
+ * (from packages in .smrt/manifest.json) to enable full CLI functionality.
140
+ */
141
+ async tryLoadUserClasses() {
142
+ const verbose = process.env.SMRT_VERBOSE === "true" || process.env.DEBUG?.includes("smrt");
143
+ if (verbose) console.log("[CLI] tryLoadUserClasses() called");
144
+ try {
145
+ if (verbose) console.log("[CLI] Loading local classes...");
146
+ await this.loadLocalClasses();
147
+ } catch (localError) {
148
+ if (verbose) console.log("[CLI] Local class loading failed (this is OK if using external packages only):", localError instanceof Error ? localError.message : "Unknown error");
149
+ }
150
+ if (verbose) console.log("[CLI] Loading external classes...");
151
+ await this.loadExternalClasses();
152
+ const { getPackageConfig } = await import("@happyvertical/smrt-config");
153
+ const { DEFAULT_CLI_CONFIG } = await import("./config-BwrFRL8L.js");
154
+ const config = getPackageConfig("cli", DEFAULT_CLI_CONFIG);
155
+ const registeredCount = ObjectRegistry.getAllClasses().size;
156
+ if (verbose || config.verbose) console.log(`[CLI] Successfully loaded ${registeredCount} SMRT objects`);
157
+ }
158
+ /**
159
+ * Load classes from local project entry point
160
+ *
161
+ * Entry point discovery order:
162
+ * 1. smrt.config.js: packages.cli.entryPoint (explicit override)
163
+ * 2. package.json: exports['.'] or main field
164
+ * 3. Fallback: ./dist/index.js
165
+ */
166
+ async loadLocalClasses() {
167
+ const { getPackageConfig } = await import("@happyvertical/smrt-config");
168
+ const { DEFAULT_CLI_CONFIG } = await import("./config-BwrFRL8L.js");
169
+ const fs = await import("node:fs");
170
+ const path = await import("node:path");
171
+ const config = getPackageConfig("cli", DEFAULT_CLI_CONFIG);
172
+ let entryPoint = config.entryPoint;
173
+ if (!entryPoint) try {
174
+ const packageJsonPath = path.resolve(process.cwd(), "package.json");
175
+ if (fs.existsSync(packageJsonPath)) {
176
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
177
+ entryPoint = packageJson.exports?.["."]?.import || packageJson.exports?.["."] || packageJson.main || "./dist/index.js";
178
+ if (config.verbose) console.log(`[CLI] Detected entry point from package.json: ${entryPoint}`);
179
+ }
180
+ } catch {
181
+ entryPoint = "./dist/index.js";
182
+ }
183
+ if (!entryPoint) entryPoint = "./dist/index.js";
184
+ const fullPath = path.resolve(process.cwd(), entryPoint);
185
+ if (!fs.existsSync(fullPath)) {
186
+ if (config.verbose) console.log(`[CLI] Entry point not found: ${fullPath}`);
187
+ return;
188
+ }
189
+ if (config.verbose) console.log(`[CLI] Loading local SMRT classes from ${entryPoint}...`);
190
+ const importedModule = await import(`file://${fullPath}`);
191
+ for (const [exportName, exportValue] of Object.entries(importedModule)) if (exportValue && typeof exportValue === "function") {
192
+ const itemClass = exportValue._itemClass;
193
+ if (itemClass) {
194
+ const tableName = itemClass.SMRT_TABLE_NAME || itemClass.name.toLowerCase();
195
+ const existing = ObjectRegistry.getClass(tableName);
196
+ if (existing && !existing.collectionConstructor) {
197
+ ObjectRegistry.registerCollection(tableName, exportValue);
198
+ if (config.verbose) console.log(`[CLI] Registered local collection ${exportName}`);
199
+ }
200
+ }
201
+ }
202
+ }
203
+ /**
204
+ * Load classes from external packages
205
+ *
206
+ * Imports the auto-generated .smrt/register.js file which contains
207
+ * static imports and registrations for all external SMRT objects.
208
+ *
209
+ * This file is generated by the consumer plugin during build.
210
+ */
211
+ async loadExternalClasses() {
212
+ const { getPackageConfig } = await import("@happyvertical/smrt-config");
213
+ const { DEFAULT_CLI_CONFIG } = await import("./config-BwrFRL8L.js");
214
+ const { loadLocalTestManifestSync } = await import("@happyvertical/smrt-core/manifest");
215
+ const fs = await import("node:fs");
216
+ const path = await import("node:path");
217
+ const config = getPackageConfig("cli", DEFAULT_CLI_CONFIG);
218
+ loadLocalTestManifestSync();
219
+ const registerPath = path.join(process.cwd(), ".smrt", "register.js");
220
+ if (!fs.existsSync(registerPath)) {
221
+ console.log("[CLI] No .smrt/register.js found - custom commands may not work");
222
+ console.log(" Run \"npm run build\" to generate class registrations");
223
+ return;
224
+ }
225
+ try {
226
+ await import(`file://${registerPath}`);
227
+ if (config.verbose) {
228
+ const count = ObjectRegistry.getAllClasses().size;
229
+ console.log(`[CLI] Loaded ${count} objects from .smrt/register.js`);
230
+ }
231
+ } catch (error) {
232
+ const msg = error instanceof Error ? error.message : "Unknown error";
233
+ console.error(`\n❌ Failed to load .smrt/register.js: ${msg}`);
234
+ console.error("\nThis usually means an installed package has non-Node.js exports (e.g., .svelte files).");
235
+ console.error("Fix the offending package, then re-run.\n");
236
+ throw error;
237
+ }
238
+ }
239
+ /**
240
+ * Handle exits safely in test mode
241
+ */
242
+ exitWithError(message, code = 1) {
243
+ if (this.isTestMode()) throw new Error(message);
244
+ console.error(message);
245
+ process.exit(code);
246
+ }
247
+ /**
248
+ * Generate CLI handler function
249
+ */
250
+ generateHandler() {
251
+ return async (argv) => {
252
+ const commands = await this.generateCommands();
253
+ const processedArgv = this.preprocessObjectCommands(argv, commands);
254
+ const parsed = parseCliArgs(processedArgv, commands, {});
255
+ await this.executeCommand(parsed, commands, processedArgv);
256
+ };
257
+ }
258
+ /**
259
+ * Preprocess argv to support space-separated object commands
260
+ *
261
+ * Converts: ['council', 'list'] → ['council:list']
262
+ * Converts: ['council', 'get', 'abc-123'] → ['council:get', 'abc-123']
263
+ *
264
+ * This enables users to type:
265
+ * smrt council list
266
+ * smrt council get abc-123
267
+ * smrt council analyze abc-123 --depth 3
268
+ *
269
+ * Instead of:
270
+ * smrt council:list
271
+ * smrt council:get abc-123
272
+ */
273
+ preprocessObjectCommands(argv, commands) {
274
+ if (argv.length < 2) return argv;
275
+ const firstArg = argv[0];
276
+ const secondArg = argv[1];
277
+ if (firstArg.includes(":")) return argv;
278
+ if (firstArg.startsWith("-")) return argv;
279
+ if (secondArg.startsWith("-")) return argv;
280
+ const combinedCommand = `${firstArg}:${secondArg}`;
281
+ if (commands.some((cmd) => cmd.name === combinedCommand || cmd.aliases?.includes(combinedCommand))) return [combinedCommand, ...argv.slice(2)];
282
+ if ((/* @__PURE__ */ new Set([
283
+ "dispatch",
284
+ "docs",
285
+ "git",
286
+ "playground"
287
+ ])).has(firstArg)) return [combinedCommand, ...argv.slice(2)];
288
+ const registeredClasses = ObjectRegistry.getAllClasses();
289
+ if (Array.from(registeredClasses.values()).some((info) => (info.name || "").toLowerCase() === firstArg.toLowerCase())) return [combinedCommand, ...argv.slice(2)];
290
+ return argv;
291
+ }
292
+ /**
293
+ * Ensure manifest and user classes are loaded.
294
+ * This is the minimum required work before any command can be executed.
295
+ * Separated from command generation to enable lazy command loading.
296
+ */
297
+ async ensureManifestLoaded() {
298
+ if (this.manifestLoaded) return;
299
+ const timing = this.context.timing;
300
+ const verbose = process.env.SMRT_VERBOSE === "true" || process.env.DEBUG?.includes("smrt");
301
+ const manifest = loadLocalTestManifestSync();
302
+ if (verbose) console.log("[CLI] Manifest loaded:", manifest ? `${Object.keys(manifest.objects || {}).length} objects` : "null");
303
+ if (manifest?.objects) for (const [name, objectDef] of Object.entries(manifest.objects)) ObjectRegistry.registerFromManifest(name, objectDef, manifest.packageName);
304
+ const classLoadStart = timing ? performance.now() : 0;
305
+ await this.tryLoadUserClasses();
306
+ if (timing) timing.classLoading = performance.now() - classLoadStart;
307
+ const registeredClasses = ObjectRegistry.getAllClasses();
308
+ this.registeredObjectNames = new Set(Array.from(registeredClasses.values()).map((info) => (info.name || "").toLowerCase()));
309
+ this.manifestLoaded = true;
310
+ }
311
+ /**
312
+ * Get commands for a specific object (lazy generation with caching)
313
+ */
314
+ async getObjectCommandsLazy(objectName) {
315
+ const lowerName = objectName.toLowerCase();
316
+ const cachedCommands = this.objectCommandsCache.get(lowerName);
317
+ if (cachedCommands) return cachedCommands;
318
+ const registeredClasses = ObjectRegistry.getAllClasses();
319
+ let actualName;
320
+ let matchedKey;
321
+ for (const [key, info] of registeredClasses) if ((info.name || key).toLowerCase() === lowerName) {
322
+ actualName = info.name || key;
323
+ matchedKey = key;
324
+ break;
325
+ }
326
+ if (!actualName || !matchedKey) return [];
327
+ const classInfo = registeredClasses.get(matchedKey);
328
+ const commands = await this.generateObjectCommands(actualName, classInfo);
329
+ this.objectCommandsCache.set(lowerName, commands);
330
+ return commands;
331
+ }
332
+ /**
333
+ * Find an object command by name (lazy lookup)
334
+ */
335
+ async findObjectCommand(commandName) {
336
+ const colonIndex = commandName.indexOf(":");
337
+ if (colonIndex === -1) return;
338
+ const objectName = commandName.slice(0, colonIndex);
339
+ await this.ensureManifestLoaded();
340
+ if (!this.registeredObjectNames?.has(objectName.toLowerCase())) return;
341
+ return (await this.getObjectCommandsLazy(objectName)).find((cmd) => cmd.name === commandName || cmd.aliases?.includes(commandName));
342
+ }
343
+ /**
344
+ * Generate all CLI commands
345
+ *
346
+ * NOTE: Object commands are now loaded LAZILY for better startup performance.
347
+ * This method only generates utility commands upfront. Object commands are
348
+ * generated on-demand when executeCommand() looks for them.
349
+ *
350
+ * For full command list (e.g., help display), use generateAllCommands().
351
+ */
352
+ async generateCommands() {
353
+ const timing = this.context.timing;
354
+ const verbose = process.env.SMRT_VERBOSE === "true" || process.env.DEBUG?.includes("smrt");
355
+ if (this.commandCache) {
356
+ if (verbose) console.log("[CLI] generateCommands() returning cached commands");
357
+ return this.commandCache;
358
+ }
359
+ if (verbose) console.log("[CLI] generateCommands() starting (lazy mode)");
360
+ await this.ensureManifestLoaded();
361
+ const commandGenStart = timing ? performance.now() : 0;
362
+ const commands = [];
363
+ const commandNames = /* @__PURE__ */ new Set();
364
+ for (const cmd of this.generateUtilityCommands()) {
365
+ if (commandNames.has(cmd.name)) {
366
+ if (verbose) console.warn(`[CLI] Skipping duplicate utility command: ${cmd.name}`);
367
+ continue;
368
+ }
369
+ commandNames.add(cmd.name);
370
+ commands.push(cmd);
371
+ }
372
+ if (timing) timing.commandGen = performance.now() - commandGenStart;
373
+ this.commandCache = commands;
374
+ return commands;
375
+ }
376
+ /**
377
+ * Generate ALL commands including lazy-loaded object commands.
378
+ * Used for help display where we need the complete list.
379
+ */
380
+ async generateAllCommands() {
381
+ if (process.env.SMRT_VERBOSE === "true" || process.env.DEBUG?.includes("smrt")) console.log("[CLI] generateAllCommands() - loading all object commands");
382
+ await this.ensureManifestLoaded();
383
+ const allCommands = [];
384
+ const commandNames = /* @__PURE__ */ new Set();
385
+ for (const cmd of this.generateUtilityCommands()) if (!commandNames.has(cmd.name)) {
386
+ commandNames.add(cmd.name);
387
+ allCommands.push(cmd);
388
+ }
389
+ const registeredClasses = ObjectRegistry.getAllClasses();
390
+ for (const [_key, classInfo] of registeredClasses) {
391
+ const objectCommands = await this.getObjectCommandsLazy(classInfo.name || _key);
392
+ for (const cmd of objectCommands) if (!commandNames.has(cmd.name)) {
393
+ commandNames.add(cmd.name);
394
+ allCommands.push(cmd);
395
+ }
396
+ }
397
+ return allCommands;
398
+ }
399
+ /**
400
+ * Generate CRUD commands for a specific object
401
+ */
402
+ async generateObjectCommands(objectName, _classInfo) {
403
+ const commands = [];
404
+ const lowerName = objectName.toLowerCase();
405
+ const cliConfig = ObjectRegistry.getConfig(objectName).cli;
406
+ if (cliConfig === false) return commands;
407
+ const excluded = (typeof cliConfig === "object" ? cliConfig.exclude : []) || [];
408
+ const included = typeof cliConfig === "object" ? cliConfig.include : null;
409
+ const shouldInclude = (command) => {
410
+ if (included && !included.includes(command)) return false;
411
+ if (excluded.includes(command)) return false;
412
+ return true;
413
+ };
414
+ if (shouldInclude("list")) commands.push({
415
+ name: `${lowerName}:list`,
416
+ description: `List ${objectName} objects`,
417
+ aliases: [`${lowerName}:ls`],
418
+ options: {
419
+ limit: {
420
+ type: "string",
421
+ description: "limit number of results",
422
+ default: "50",
423
+ short: "l"
424
+ },
425
+ offset: {
426
+ type: "string",
427
+ description: "offset for pagination",
428
+ default: "0",
429
+ short: "o"
430
+ },
431
+ "order-by": {
432
+ type: "string",
433
+ description: "field to order by"
434
+ },
435
+ where: {
436
+ type: "string",
437
+ description: "filter conditions as JSON"
438
+ },
439
+ format: {
440
+ type: "string",
441
+ description: "output format (table|json)",
442
+ default: "table"
443
+ }
444
+ },
445
+ handler: async (_args, options) => {
446
+ await this.handleList(objectName, options);
447
+ }
448
+ });
449
+ if (shouldInclude("get")) commands.push({
450
+ name: `${lowerName}:get`,
451
+ description: `Get ${objectName} by ID or slug`,
452
+ aliases: [`${lowerName}:show`],
453
+ args: ["id"],
454
+ options: { format: {
455
+ type: "string",
456
+ description: "output format (json|yaml)",
457
+ default: "json"
458
+ } },
459
+ handler: async (args, options) => {
460
+ await this.handleGet(objectName, args[0], options);
461
+ }
462
+ });
463
+ if (shouldInclude("create")) {
464
+ const options = {
465
+ interactive: {
466
+ type: "boolean",
467
+ description: "interactive mode with prompts"
468
+ },
469
+ "from-file": {
470
+ type: "string",
471
+ description: "create from JSON file"
472
+ }
473
+ };
474
+ const fields = ObjectRegistry.getFields(objectName);
475
+ for (const [fieldName, field] of fields) {
476
+ const optionName = fieldName.replace(/_/g, "-");
477
+ options[optionName] = {
478
+ type: "string",
479
+ description: field.options?.description || `${objectName} ${fieldName}`
480
+ };
481
+ }
482
+ commands.push({
483
+ name: `${lowerName}:create`,
484
+ description: `Create new ${objectName}`,
485
+ aliases: [`${lowerName}:new`],
486
+ options,
487
+ handler: async (_args, options) => {
488
+ await this.handleCreate(objectName, options);
489
+ }
490
+ });
491
+ }
492
+ if (shouldInclude("update")) {
493
+ const options = {
494
+ interactive: {
495
+ type: "boolean",
496
+ description: "interactive mode with prompts"
497
+ },
498
+ "from-file": {
499
+ type: "string",
500
+ description: "update from JSON file"
501
+ }
502
+ };
503
+ const fields = ObjectRegistry.getFields(objectName);
504
+ for (const [fieldName, field] of fields) {
505
+ const optionName = fieldName.replace(/_/g, "-");
506
+ options[optionName] = {
507
+ type: "string",
508
+ description: field.options?.description || `${objectName} ${fieldName}`
509
+ };
510
+ }
511
+ commands.push({
512
+ name: `${lowerName}:update`,
513
+ description: `Update ${objectName}`,
514
+ aliases: [`${lowerName}:edit`],
515
+ args: ["id"],
516
+ options,
517
+ handler: async (args, options) => {
518
+ await this.handleUpdate(objectName, args[0], options);
519
+ }
520
+ });
521
+ }
522
+ if (shouldInclude("delete")) commands.push({
523
+ name: `${lowerName}:delete`,
524
+ description: `Delete ${objectName}`,
525
+ aliases: [`${lowerName}:rm`],
526
+ args: ["id"],
527
+ options: { force: {
528
+ type: "boolean",
529
+ description: "skip confirmation prompt"
530
+ } },
531
+ handler: async (args, options) => {
532
+ await this.handleDelete(objectName, args[0], options);
533
+ }
534
+ });
535
+ const methods = await ObjectRegistry.getAllMethods(objectName);
536
+ const crudOperations = [
537
+ "list",
538
+ "get",
539
+ "create",
540
+ "update",
541
+ "delete"
542
+ ];
543
+ const hasCustomMethodsInInclude = included?.some((item) => !crudOperations.includes(item));
544
+ for (const [methodName, methodDef] of methods) {
545
+ const shouldIncludeMethod = () => {
546
+ if (!methodDef.isPublic) return false;
547
+ if (excluded.includes(methodName)) return false;
548
+ if (hasCustomMethodsInInclude && included && !included.includes(methodName)) return false;
549
+ return true;
550
+ };
551
+ if (!shouldIncludeMethod()) continue;
552
+ const methodOptions = { json: {
553
+ type: "boolean",
554
+ description: "Output as JSON only (suppress other output)"
555
+ } };
556
+ for (const param of methodDef.parameters || []) {
557
+ const typeStr = param.type || "";
558
+ if (this.isObjectTypeParameter(typeStr)) {
559
+ const match = typeStr.match(/\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/);
560
+ if (match) {
561
+ const propMatches = match[1].matchAll(/(\w+)(\?)?:\s*([^;]+)/g);
562
+ for (const propMatch of propMatches) {
563
+ const [, propName, isOptional, propType] = propMatch;
564
+ const optionName = propName.replace(/([A-Z])/g, "-$1").toLowerCase();
565
+ if (propType.includes("import(")) methodOptions[optionName] = {
566
+ type: "string",
567
+ description: `JSON object${isOptional ? " (optional)" : ""}`
568
+ };
569
+ else {
570
+ const trimmedType = propType.trim();
571
+ methodOptions[optionName] = {
572
+ type: trimmedType === "boolean" ? "boolean" : "string",
573
+ description: `${trimmedType}${isOptional ? " (optional)" : ""}`
574
+ };
575
+ }
576
+ }
577
+ }
578
+ } else {
579
+ const optionName = param.name.replace(/([A-Z])/g, "-$1").toLowerCase();
580
+ methodOptions[optionName] = {
581
+ type: (param.type || "").trim() === "boolean" ? "boolean" : "string",
582
+ description: `${param.type}${param.optional ? " (optional)" : ""}`,
583
+ ...param.default !== void 0 && { default: String(param.default) }
584
+ };
585
+ }
586
+ }
587
+ const needsInstance = (methodDef.parameters || [])[0]?.name === "id";
588
+ commands.push({
589
+ name: `${lowerName}:${methodName}`,
590
+ description: methodDef.description || `Execute ${methodName} on ${objectName}`,
591
+ args: needsInstance ? ["id"] : [],
592
+ options: methodOptions,
593
+ handler: async (args, options) => {
594
+ if (needsInstance) await this.handleCustomMethod(objectName, args[0], methodName, options);
595
+ else await this.handleSingletonMethod(objectName, methodName, options, methodDef);
596
+ }
597
+ });
598
+ }
599
+ return commands;
600
+ }
601
+ /**
602
+ * Execute a parsed command
603
+ */
604
+ async executeCommand(parsed, commands, processedArgv = process.argv.slice(2)) {
605
+ if (!parsed.command) {
606
+ const allCommands = await this.generateAllCommands();
607
+ await this.showHelp(allCommands);
608
+ return;
609
+ }
610
+ let command = commands.find((cmd) => cmd.name === parsed.command || parsed.command && cmd.aliases && cmd.aliases.includes(parsed.command));
611
+ if (!command && parsed.command) command = await this.findObjectCommand(parsed.command);
612
+ if (command) {
613
+ const requiredArgCount = countRequiredArgs(command.args);
614
+ if (parsed.args.length < requiredArgCount) {
615
+ const missingArgs = command.args?.slice(parsed.args.length).filter((arg) => !arg.startsWith("[") || !arg.endsWith("]"));
616
+ this.exitWithError(`Missing required arguments: ${missingArgs?.join(", ") || ""}`);
617
+ return;
618
+ }
619
+ if (!command.handler) {
620
+ this.exitWithError(`Command '${parsed.command}' has no handler defined`);
621
+ return;
622
+ }
623
+ try {
624
+ await command.handler(parsed.args, parsed.options);
625
+ return;
626
+ } catch (error) {
627
+ this.exitWithError(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
628
+ return;
629
+ }
630
+ }
631
+ const [gnodeCommands, generateCommands, gitCommands, initCommands, utilityCommands, dispatchCommands, docsCommands, playgroundCommands] = await Promise.all([
632
+ getGnodeCommands(),
633
+ getGenerateCommands(),
634
+ getGitCommands(),
635
+ getInitCommands(),
636
+ getUtilityCommands(),
637
+ getDispatchCommands(),
638
+ getDocsCommands(),
639
+ getPlaygroundCommands()
640
+ ]);
641
+ const builtInCommands = {
642
+ ...gnodeCommands,
643
+ ...generateCommands,
644
+ ...gitCommands,
645
+ ...initCommands,
646
+ ...utilityCommands,
647
+ ...dispatchCommands,
648
+ ...docsCommands,
649
+ ...playgroundCommands
650
+ };
651
+ const builtInCommand = builtInCommands[parsed.command] ?? Object.values(builtInCommands).find((cmd) => cmd.name === parsed.command || cmd.aliases?.includes(parsed.command ?? ""));
652
+ if (builtInCommand) {
653
+ const requiredArgCount = countRequiredArgs(builtInCommand.args);
654
+ if (parsed.args.length < requiredArgCount) {
655
+ const missingArgs = builtInCommand.args?.slice(parsed.args.length).filter((arg) => !arg.startsWith("[") || !arg.endsWith("]"));
656
+ this.exitWithError(`Missing required arguments: ${missingArgs?.join(", ") || ""}`);
657
+ return;
658
+ }
659
+ if (!builtInCommand.handler) {
660
+ this.exitWithError(`Command '${parsed.command}' has no handler defined`);
661
+ return;
662
+ }
663
+ const reParsed = parseCliArgs(processedArgv, [builtInCommand], {});
664
+ try {
665
+ await builtInCommand.handler(reParsed.args, reParsed.options);
666
+ return;
667
+ } catch (error) {
668
+ this.exitWithError(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
669
+ return;
670
+ }
671
+ }
672
+ await this.ensureManifestLoaded();
673
+ const registeredClasses = ObjectRegistry.getAllClasses();
674
+ let matchingObject;
675
+ for (const [_key, info] of registeredClasses) if ((info.name || _key).toLowerCase() === parsed.command?.toLowerCase()) {
676
+ matchingObject = info.name || _key;
677
+ break;
678
+ }
679
+ if (matchingObject) {
680
+ const objectCommands = await this.getObjectCommandsLazy(matchingObject);
681
+ await this.showObjectHelp(matchingObject, objectCommands);
682
+ return;
683
+ }
684
+ this.exitWithError(`Unknown command '${parsed.command}'`);
685
+ }
686
+ /**
687
+ * Show help for a specific object and its available commands
688
+ */
689
+ async showObjectHelp(objectName, objectCommands) {
690
+ const classInfo = ObjectRegistry.getClass(objectName);
691
+ const lowerName = objectName.toLowerCase();
692
+ console.log(`\n${objectName}`);
693
+ console.log("=".repeat(objectName.length));
694
+ if (classInfo?.packageName) console.log(`Package: ${classInfo.packageName}`);
695
+ if (objectCommands.length === 0) {
696
+ console.log("\nNo CLI commands available for this object.");
697
+ console.log("Check that cli: true or cli: { include: [...] } is set in @smrt() decorator.");
698
+ return;
699
+ }
700
+ console.log("\nAvailable commands:");
701
+ for (const cmd of objectCommands) {
702
+ const cmdName = cmd.name.replace(`${lowerName}:`, "");
703
+ const args = cmd.args ? ` ${cmd.args.map((arg) => `<${arg}>`).join(" ")}` : "";
704
+ console.log(` smrt ${lowerName} ${cmdName}${args}`);
705
+ console.log(` ${cmd.description}`);
706
+ if (cmd.options && Object.keys(cmd.options).length > 0) for (const [optName, opt] of Object.entries(cmd.options)) {
707
+ const short = opt.short ? `-${opt.short}, ` : "";
708
+ const def = opt.default ? ` (default: ${opt.default})` : "";
709
+ console.log(` ${short}--${optName}${def}`);
710
+ }
711
+ console.log();
712
+ }
713
+ const fields = ObjectRegistry.getFields(objectName);
714
+ if (fields.size > 0) {
715
+ console.log("Fields:");
716
+ for (const [fieldName, field] of fields) {
717
+ const required = field.options?.required ? " (required)" : "";
718
+ console.log(` ${fieldName}: ${field.type}${required}`);
719
+ }
720
+ }
721
+ }
722
+ /**
723
+ * Generate utility commands
724
+ */
725
+ generateUtilityCommands() {
726
+ const commands = [];
727
+ commands.push({
728
+ name: "objects",
729
+ description: "List all registered smrt objects",
730
+ aliases: ["ls"],
731
+ options: {
732
+ verbose: {
733
+ type: "boolean",
734
+ description: "Show detailed output including methods",
735
+ short: "v"
736
+ },
737
+ json: {
738
+ type: "boolean",
739
+ description: "Output as JSON"
740
+ }
741
+ },
742
+ handler: async (_args, options) => {
743
+ const registeredClasses = ObjectRegistry.getAllClasses();
744
+ if (registeredClasses.size === 0) {
745
+ console.log("No SMRT objects found.");
746
+ console.log("\nTo discover objects:");
747
+ console.log(" • Build your project: npm run build");
748
+ console.log(" • Ensure .smrt/register.js exists");
749
+ console.log(" • Run: smrt introspect for details");
750
+ return;
751
+ }
752
+ if (options.json) {
753
+ const output = {};
754
+ for (const [key, classInfo] of registeredClasses) {
755
+ const displayName = classInfo.name || key;
756
+ const config = ObjectRegistry.getConfig(key);
757
+ const fields = ObjectRegistry.getFields(key);
758
+ const methods = await ObjectRegistry.getAllMethods(key);
759
+ output[classInfo.qualifiedName || displayName] = {
760
+ name: displayName,
761
+ package: classInfo.packageName || "project",
762
+ hasConstructor: !!classInfo.constructor,
763
+ hasCollection: !!classInfo.collectionConstructor,
764
+ config,
765
+ fields: Object.fromEntries(fields),
766
+ methods: Object.fromEntries(methods)
767
+ };
768
+ }
769
+ console.log(JSON.stringify(output, null, 2));
770
+ return;
771
+ }
772
+ console.log("Registered SMRT objects:\n");
773
+ const byPackage = /* @__PURE__ */ new Map();
774
+ for (const [key, classInfo] of registeredClasses) {
775
+ const pkg = classInfo.packageName || "project";
776
+ if (!byPackage.has(pkg)) byPackage.set(pkg, []);
777
+ byPackage.get(pkg)?.push({
778
+ display: classInfo.name || key,
779
+ key
780
+ });
781
+ }
782
+ for (const [pkg, entries] of byPackage) {
783
+ console.log(` ${pkg}:`);
784
+ for (const { display: objName, key: objKey } of entries) {
785
+ const cliConfig = ObjectRegistry.getConfig(objKey).cli;
786
+ let cliMethods = [];
787
+ if (cliConfig) {
788
+ const methods = await ObjectRegistry.getAllMethods(objKey);
789
+ const methodNames = Array.from(methods.keys());
790
+ if (typeof cliConfig === "object" && cliConfig.include) cliMethods = methodNames.filter((m) => cliConfig.include?.includes(m) && methods.get(m)?.isPublic);
791
+ else if (cliConfig === true) cliMethods = methodNames.filter((m) => methods.get(m)?.isPublic);
792
+ }
793
+ if (options.verbose) {
794
+ console.log(` • ${objName}`);
795
+ if (cliMethods.length > 0) console.log(` CLI: ${cliMethods.join(", ")}`);
796
+ const fields = ObjectRegistry.getFields(objKey);
797
+ const fieldNames = Array.from(fields.keys()).slice(0, 5);
798
+ if (fieldNames.length > 0) console.log(` Fields: ${fieldNames.join(", ")}${fields.size > 5 ? "..." : ""}`);
799
+ } else {
800
+ const methodStr = cliMethods.length > 0 ? ` → ${cliMethods.join(", ")}` : "";
801
+ console.log(` • ${objName}${methodStr}`);
802
+ }
803
+ }
804
+ console.log();
805
+ }
806
+ console.log(`Total: ${registeredClasses.size} objects from ${byPackage.size} source(s)`);
807
+ }
808
+ });
809
+ commands.push({
810
+ name: "schema",
811
+ description: "Show schema for an object",
812
+ args: ["object"],
813
+ handler: this.createSchemaHandler()
814
+ });
815
+ commands.push({
816
+ name: "help",
817
+ description: "Show help information",
818
+ aliases: ["h"],
819
+ handler: async (_args, _options) => {
820
+ await this.showHelp(commands);
821
+ }
822
+ });
823
+ commands.push({
824
+ name: "version",
825
+ description: "Show version information",
826
+ aliases: ["v"],
827
+ handler: async (_args, _options) => {
828
+ console.log(`${this.config.name} v${this.config.version}`);
829
+ }
830
+ });
831
+ commands.push({
832
+ name: "status",
833
+ description: "Show system status",
834
+ handler: async (_args, _options) => {
835
+ console.log("System Status:");
836
+ console.log(`- CLI: ${this.config.name} v${this.config.version}`);
837
+ console.log(`- Database: ${this.context.db ? "Connected" : "Not connected"}`);
838
+ console.log(`- AI: ${this.context.ai ? "Available" : "Not available"}`);
839
+ console.log(`- User: ${this.context.user?.id || "Not authenticated"}`);
840
+ }
841
+ });
842
+ return commands;
843
+ }
844
+ /**
845
+ * Create schema command handler
846
+ */
847
+ createSchemaHandler() {
848
+ return async (args, _options) => {
849
+ const objectName = args[0];
850
+ const fields = ObjectRegistry.getFields(objectName);
851
+ if (fields.size === 0) {
852
+ this.exitWithError(`Object ${objectName} not found`);
853
+ return;
854
+ }
855
+ console.log(`Schema for ${objectName}:`);
856
+ for (const [fieldName, field] of fields) {
857
+ console.log(` ${fieldName}: ${field.type}${field.options?.required ? " (required)" : ""}`);
858
+ if (field.options?.description) console.log(` ${field.options.description}`);
859
+ }
860
+ };
861
+ }
862
+ /**
863
+ * Show help information
864
+ */
865
+ async showHelp(commands) {
866
+ console.log(`${this.config.name} v${this.config.version}`);
867
+ console.log(this.config.description);
868
+ console.log();
869
+ const [gnodeCommands, generateCommands, gitCommands, initCommands, utilityCommands, dispatchCommands, docsCommands, playgroundCommands] = await Promise.all([
870
+ getGnodeCommands(),
871
+ getGenerateCommands(),
872
+ getGitCommands(),
873
+ getInitCommands(),
874
+ getUtilityCommands(),
875
+ getDispatchCommands(),
876
+ getDocsCommands(),
877
+ getPlaygroundCommands()
878
+ ]);
879
+ console.log("Project Setup:");
880
+ for (const command of Object.values(initCommands)) this.showCommandHelp(command);
881
+ console.log();
882
+ console.log("Playground:");
883
+ for (const command of Object.values(playgroundCommands)) this.showCommandHelp(command);
884
+ console.log();
885
+ console.log("Utility Commands:");
886
+ for (const command of Object.values(utilityCommands)) this.showCommandHelp(command);
887
+ console.log();
888
+ console.log("Dispatch (Inter-Agent Communication):");
889
+ for (const command of Object.values(dispatchCommands)) this.showCommandHelp(command);
890
+ console.log();
891
+ console.log("Git Integration:");
892
+ for (const command of Object.values(gitCommands)) this.showCommandHelp(command);
893
+ console.log();
894
+ console.log("Gnode Commands:");
895
+ for (const command of Object.values(gnodeCommands)) this.showCommandHelp(command);
896
+ console.log("Code Generation:");
897
+ for (const command of Object.values(generateCommands)) this.showCommandHelp(command);
898
+ console.log("Documentation:");
899
+ for (const command of Object.values(docsCommands)) this.showCommandHelp(command);
900
+ console.log();
901
+ const builtInUtilityCommands = commands.filter((cmd) => cmd.name === "objects" || cmd.name === "schema" || cmd.name === "help" || cmd.name === "version" || cmd.name === "status");
902
+ if (builtInUtilityCommands.length > 0) {
903
+ console.log("Object Utilities:");
904
+ for (const command of builtInUtilityCommands) this.showCommandHelp(command);
905
+ }
906
+ const objectCommands = commands.filter((cmd) => !builtInUtilityCommands.includes(cmd));
907
+ if (objectCommands.length > 0) {
908
+ console.log("Object Commands (auto-generated):");
909
+ for (const command of objectCommands) this.showCommandHelp(command);
910
+ }
911
+ }
912
+ /**
913
+ * Show help for a single command
914
+ */
915
+ showCommandHelp(command) {
916
+ const aliases = command.aliases ? ` (${command.aliases.join(", ")})` : "";
917
+ const args = command.args ? ` ${command.args.map((arg) => `<${arg}>`).join(" ")}` : "";
918
+ console.log(` ${command.name}${args}${aliases}`);
919
+ console.log(` ${command.description}`);
920
+ if (command.options) for (const [name, option] of Object.entries(command.options)) {
921
+ const short = option.short ? `-${option.short}, ` : "";
922
+ console.log(` ${short}--${name}: ${option.description}`);
923
+ }
924
+ console.log();
925
+ }
926
+ /**
927
+ * Create a simple spinner
928
+ */
929
+ createSpinner(text) {
930
+ const isTTY = process.stdout.isTTY && typeof process.stdout.clearLine === "function" && typeof process.stdout.cursorTo === "function";
931
+ if (this.config.colors && isTTY) {
932
+ process.stdout.write(`⠋ ${text}`);
933
+ return {
934
+ succeed: (successText) => {
935
+ process.stdout.clearLine(0);
936
+ process.stdout.cursorTo(0);
937
+ console.log(`✅ ${successText || text}`);
938
+ },
939
+ fail: (errorText) => {
940
+ process.stdout.clearLine(0);
941
+ process.stdout.cursorTo(0);
942
+ console.log(`❌ ${errorText || text}`);
943
+ }
944
+ };
945
+ }
946
+ console.log(text);
947
+ return {
948
+ succeed: (successText) => console.log(successText || "Done"),
949
+ fail: (errorText) => console.log(errorText || "Failed")
950
+ };
951
+ }
952
+ /**
953
+ * Prompt for input
954
+ */
955
+ async prompt(message) {
956
+ const rl = createInterface({
957
+ input: process.stdin,
958
+ output: process.stdout
959
+ });
960
+ return new Promise((resolve) => {
961
+ rl.question(`${message} `, (answer) => {
962
+ rl.close();
963
+ resolve(answer);
964
+ });
965
+ });
966
+ }
967
+ /**
968
+ * Confirm prompt
969
+ */
970
+ async confirm(message) {
971
+ return (await this.prompt(`${message} (y/n)`)).toLowerCase().startsWith("y");
972
+ }
973
+ /**
974
+ * Handle LIST command
975
+ */
976
+ async handleList(objectName, options) {
977
+ const spinner = this.createSpinner(`Listing ${objectName} objects...`);
978
+ try {
979
+ const collection = await this.getCollection(objectName);
980
+ const listOptions = {
981
+ limit: Number.parseInt(String(options.limit), 10),
982
+ offset: Number.parseInt(String(options.offset), 10)
983
+ };
984
+ const orderBy = options["order-by"] ?? options.orderBy;
985
+ if (typeof orderBy === "string" && orderBy) listOptions.orderBy = orderBy;
986
+ if (typeof options.where === "string" && options.where) listOptions.where = JSON.parse(options.where);
987
+ const results = await collection.list(listOptions);
988
+ spinner.succeed(`Found ${results.length} ${objectName} objects`);
989
+ if (options.format === "json") console.log(JSON.stringify(results, null, 2));
990
+ else this.displayTable(results, objectName);
991
+ } catch (error) {
992
+ spinner.fail(`Failed to list ${objectName} objects`);
993
+ this.exitWithError(error instanceof Error ? error.message : "Unknown error");
994
+ }
995
+ }
996
+ /**
997
+ * Handle GET command
998
+ */
999
+ async handleGet(objectName, id, options) {
1000
+ const spinner = this.createSpinner(`Getting ${objectName}...`);
1001
+ try {
1002
+ const result = await (await this.getCollection(objectName)).get(id);
1003
+ if (!result) {
1004
+ spinner.fail(`${objectName} not found`);
1005
+ this.exitWithError(`${objectName} not found`);
1006
+ return;
1007
+ }
1008
+ spinner.succeed(`Found ${objectName}`);
1009
+ if (options.format === "yaml") console.log(this.toYamlString(result));
1010
+ else console.log(JSON.stringify(result, null, 2));
1011
+ } catch (error) {
1012
+ spinner.fail(`Failed to get ${objectName}`);
1013
+ this.exitWithError(error instanceof Error ? error.message : "Unknown error");
1014
+ }
1015
+ }
1016
+ /**
1017
+ * Handle CREATE command
1018
+ */
1019
+ async handleCreate(objectName, options) {
1020
+ try {
1021
+ let data = {};
1022
+ const fromFile = options["from-file"] ?? options.fromFile;
1023
+ if (typeof fromFile === "string" && fromFile) {
1024
+ const content = await (await import("node:fs/promises")).readFile(fromFile, "utf-8");
1025
+ data = JSON.parse(content);
1026
+ } else if (options.interactive && this.config.prompt) data = await this.promptForFields(objectName, {});
1027
+ else {
1028
+ const fields = ObjectRegistry.getFields(objectName);
1029
+ for (const [fieldName] of fields) {
1030
+ const optionName = fieldName.replace(/_/g, "-");
1031
+ if (options[optionName] !== void 0) data[fieldName] = this.parseFieldValue(String(options[optionName]));
1032
+ }
1033
+ }
1034
+ const spinner = this.createSpinner(`Creating ${objectName}...`);
1035
+ const result = await (await this.getCollection(objectName)).create(data);
1036
+ await result.save();
1037
+ spinner.succeed(`Created ${objectName} with ID: ${result.id}`);
1038
+ if (!options.quiet) console.log(JSON.stringify(result, null, 2));
1039
+ } catch (error) {
1040
+ this.exitWithError(error instanceof Error ? error.message : "Unknown error");
1041
+ }
1042
+ }
1043
+ /**
1044
+ * Handle UPDATE command
1045
+ */
1046
+ async handleUpdate(objectName, id, options) {
1047
+ try {
1048
+ const existing = await (await this.getCollection(objectName)).get(id);
1049
+ if (!existing) {
1050
+ this.exitWithError(`${objectName} not found`);
1051
+ return;
1052
+ }
1053
+ let data = {};
1054
+ const fromFile = options["from-file"] ?? options.fromFile;
1055
+ if (typeof fromFile === "string" && fromFile) {
1056
+ const content = await (await import("node:fs/promises")).readFile(fromFile, "utf-8");
1057
+ data = JSON.parse(content);
1058
+ } else if (options.interactive && this.config.prompt) data = await this.promptForFields(objectName, existing);
1059
+ else {
1060
+ const fields = ObjectRegistry.getFields(objectName);
1061
+ for (const [fieldName] of fields) {
1062
+ const optionName = fieldName.replace(/_/g, "-");
1063
+ if (options[optionName] !== void 0) data[fieldName] = this.parseFieldValue(String(options[optionName]));
1064
+ }
1065
+ }
1066
+ const spinner = this.createSpinner(`Updating ${objectName}...`);
1067
+ Object.assign(existing, data);
1068
+ await existing.save();
1069
+ spinner.succeed(`Updated ${objectName}`);
1070
+ if (!options.quiet) console.log(JSON.stringify(existing, null, 2));
1071
+ } catch (error) {
1072
+ this.exitWithError(error instanceof Error ? error.message : "Unknown error");
1073
+ }
1074
+ }
1075
+ /**
1076
+ * Handle DELETE command
1077
+ */
1078
+ async handleDelete(objectName, id, options) {
1079
+ try {
1080
+ const existing = await (await this.getCollection(objectName)).get(id);
1081
+ if (!existing) {
1082
+ this.exitWithError(`${objectName} not found`);
1083
+ return;
1084
+ }
1085
+ if (!options.force && this.config.prompt) {
1086
+ const label = existing.name || existing.slug || existing.id;
1087
+ if (!await this.confirm(`Are you sure you want to delete ${objectName} "${label}"?`)) {
1088
+ console.log("Cancelled");
1089
+ return;
1090
+ }
1091
+ }
1092
+ const spinner = this.createSpinner(`Deleting ${objectName}...`);
1093
+ await existing.delete();
1094
+ spinner.succeed(`Deleted ${objectName}`);
1095
+ } catch (error) {
1096
+ this.exitWithError(error instanceof Error ? error.message : "Unknown error");
1097
+ }
1098
+ }
1099
+ /**
1100
+ * Handle custom method execution
1101
+ */
1102
+ async handleCustomMethod(objectName, id, methodName, options) {
1103
+ try {
1104
+ const obj = await (await this.getCollection(objectName)).get(id);
1105
+ if (!obj) {
1106
+ this.exitWithError(`${objectName} not found`);
1107
+ return;
1108
+ }
1109
+ const methodDef = (await ObjectRegistry.getAllMethods(objectName)).get(methodName);
1110
+ if (!methodDef) {
1111
+ this.exitWithError(`Method ${methodName} not found on ${objectName}`);
1112
+ return;
1113
+ }
1114
+ const spinner = options.json === true ? {
1115
+ succeed: (_text) => {},
1116
+ fail: (msg) => {
1117
+ if (msg) console.error(msg);
1118
+ }
1119
+ } : this.createSpinner(`Executing ${methodName} on ${objectName}...`);
1120
+ const methodParams = methodDef.parameters || [];
1121
+ const methodCallArgs = [];
1122
+ for (const param of methodParams) {
1123
+ const typeStr = param.type || "";
1124
+ if (this.isObjectTypeParameter(typeStr)) {
1125
+ const objArg = {};
1126
+ const match = typeStr.match(/\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/);
1127
+ if (match) {
1128
+ const propMatches = match[1].matchAll(/(\w+)(\?)?:\s*([^;]+)/g);
1129
+ for (const propMatch of propMatches) {
1130
+ const [, propName] = propMatch;
1131
+ const optionName = propName.replace(/([A-Z])/g, "-$1").toLowerCase();
1132
+ if (options[optionName] !== void 0) {
1133
+ let value = options[optionName];
1134
+ if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) try {
1135
+ value = JSON.parse(value);
1136
+ } catch {}
1137
+ objArg[propName] = value;
1138
+ }
1139
+ }
1140
+ }
1141
+ if (Object.keys(objArg).length > 0) methodCallArgs.push(objArg);
1142
+ else if (!param.optional) methodCallArgs.push({});
1143
+ else methodCallArgs.push(void 0);
1144
+ } else {
1145
+ const optionName = param.name.replace(/([A-Z])/g, "-$1").toLowerCase();
1146
+ if (options[optionName] !== void 0) methodCallArgs.push(options[optionName]);
1147
+ else if (param.default !== void 0) methodCallArgs.push(param.default);
1148
+ else methodCallArgs.push(void 0);
1149
+ }
1150
+ }
1151
+ const method = obj[methodName];
1152
+ if (typeof method !== "function") {
1153
+ spinner.fail(`Method ${methodName} is not a function`);
1154
+ this.exitWithError(`Method ${methodName} is not callable`);
1155
+ return;
1156
+ }
1157
+ const result = await method.call(obj, ...methodCallArgs);
1158
+ spinner.succeed(`Executed ${methodName}`);
1159
+ console.log(JSON.stringify(result, null, 2));
1160
+ } catch (error) {
1161
+ this.exitWithError(error instanceof Error ? error.message : "Unknown error");
1162
+ }
1163
+ }
1164
+ /**
1165
+ * Handle singleton method (no parameters, no database lookup)
1166
+ * Creates a new instance with proper config and calls the method
1167
+ */
1168
+ async handleSingletonMethod(objectName, methodName, options, methodDef) {
1169
+ try {
1170
+ const classInfo = ObjectRegistry.getClass(objectName);
1171
+ if (!classInfo || !classInfo.constructor) {
1172
+ const availableObjects = Array.from(ObjectRegistry.getAllClasses().values()).map((info) => info.name);
1173
+ const availableList = availableObjects.length > 0 ? `Available objects:\n ${availableObjects.join("\n ")}` : "No objects registered. Run \"npm run build\" to generate registrations.";
1174
+ this.exitWithError(`Object class '${objectName}' not found.\n\n${availableList}\n\nTroubleshooting:\n1. Run "npm run build" to generate .smrt/register.js\n2. Ensure package exports classes correctly\n3. Run "smrt doctor" for diagnostics`);
1175
+ return;
1176
+ }
1177
+ const jsonMode = options.json === true;
1178
+ const spinner = jsonMode ? {
1179
+ succeed: (_text) => {},
1180
+ fail: (msg) => {
1181
+ if (msg) console.error(msg);
1182
+ }
1183
+ } : this.createSpinner(`Executing ${methodName} on ${objectName}...`);
1184
+ const { getConfig, getPackageConfig, getModuleConfig } = await import("@happyvertical/smrt-config");
1185
+ const smrtConfig = getConfig() || {};
1186
+ const moduleConfig = getModuleConfig(objectName.toLowerCase(), {});
1187
+ const { DEFAULT_CLI_CONFIG } = await import("./config-BwrFRL8L.js");
1188
+ const cliConfig = getPackageConfig("cli", DEFAULT_CLI_CONFIG);
1189
+ let db = this.context.db;
1190
+ if (!db && cliConfig?.database?.url) {
1191
+ const { getDatabase } = await import("@happyvertical/sql");
1192
+ db = await getDatabase({
1193
+ type: cliConfig.database.type || "sqlite",
1194
+ url: cliConfig.database.url
1195
+ });
1196
+ }
1197
+ const isManifestStub = classInfo.constructor?._isManifestStub === true;
1198
+ if (process.env.DEBUG) {
1199
+ console.log(`[DEBUG] ${objectName} constructor info:`);
1200
+ console.log(` - isManifestStub: ${isManifestStub}`);
1201
+ console.log(` - constructor name: ${classInfo.constructor?.name || "undefined"}`);
1202
+ console.log(` - packageName: ${classInfo.packageName || "local"}`);
1203
+ }
1204
+ if (isManifestStub) {
1205
+ this.exitWithError(`${objectName} is registered from manifest but the real class wasn't loaded.\n\nThis usually means:\n1. The .smrt/register.js file doesn't import the class\n2. The package doesn't export the class properly\n3. The class name in the package doesn't match the manifest\n\nTry:\n- Run 'npm run build' to regenerate .smrt/register.js\n- Check that the package exports the ${objectName} class\n- Check .smrt/register.js imports match the package exports`);
1206
+ return;
1207
+ }
1208
+ if (typeof classInfo.constructor !== "function") {
1209
+ this.exitWithError(`${objectName} constructor is not available.\nThis usually means the class wasn't properly exported or registered.\nCheck that the package exports the class and .smrt/register.js imports it.`);
1210
+ return;
1211
+ }
1212
+ const useCliDb = db && !moduleConfig?.db;
1213
+ const instanceConfig = {
1214
+ ...smrtConfig,
1215
+ ...moduleConfig,
1216
+ ...useCliDb ? { db } : {},
1217
+ ...this.context.ai ? { ai: this.context.ai } : {},
1218
+ ...jsonMode && { silent: true }
1219
+ };
1220
+ const obj = new classInfo.constructor(instanceConfig);
1221
+ if (typeof obj.initialize === "function") await obj.initialize();
1222
+ const method = obj[methodName];
1223
+ if (typeof method !== "function") {
1224
+ spinner.fail(`Method ${methodName} is not a function`);
1225
+ this.exitWithError(`Method ${methodName} is not callable`);
1226
+ return;
1227
+ }
1228
+ const methodParams = methodDef?.parameters || [];
1229
+ const methodCallArgs = [];
1230
+ for (const param of methodParams) {
1231
+ const typeStr = param.type || "";
1232
+ if (this.isObjectTypeParameter(typeStr)) {
1233
+ const objArg = {};
1234
+ const match = typeStr.match(/\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/);
1235
+ if (match) {
1236
+ const propMatches = match[1].matchAll(/(\w+)(\?)?:\s*([^;]+)/g);
1237
+ for (const propMatch of propMatches) {
1238
+ const [, propName] = propMatch;
1239
+ const optionName = propName.replace(/([A-Z])/g, "-$1").toLowerCase();
1240
+ if (options[optionName] !== void 0) {
1241
+ let value = options[optionName];
1242
+ if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) try {
1243
+ value = JSON.parse(value);
1244
+ } catch {}
1245
+ objArg[propName] = value;
1246
+ }
1247
+ }
1248
+ }
1249
+ if (Object.keys(objArg).length > 0) methodCallArgs.push(objArg);
1250
+ else if (!param.optional) methodCallArgs.push({});
1251
+ else methodCallArgs.push(void 0);
1252
+ } else {
1253
+ const optionName = param.name.replace(/([A-Z])/g, "-$1").toLowerCase();
1254
+ if (options[optionName] !== void 0) methodCallArgs.push(options[optionName]);
1255
+ else if (param.default !== void 0) methodCallArgs.push(param.default);
1256
+ else methodCallArgs.push(void 0);
1257
+ }
1258
+ }
1259
+ const result = await method.call(obj, ...methodCallArgs);
1260
+ spinner.succeed(`Executed ${methodName}`);
1261
+ if (result !== void 0) console.log(JSON.stringify(result, null, 2));
1262
+ } catch (error) {
1263
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1264
+ const errorStack = error instanceof Error ? error.stack : void 0;
1265
+ console.error(`\nError executing ${objectName}.${methodName}():`);
1266
+ console.error(` ${errorMessage}`);
1267
+ if (errorStack && process.env.DEBUG) {
1268
+ console.error("\nStack trace:");
1269
+ console.error(errorStack);
1270
+ }
1271
+ const schemaGuidance = errorMessage.includes("Run 'smrt db:migrate'");
1272
+ console.error(schemaGuidance ? "\nTip: Prepare the database schema before running generated CLI commands." : "\nTip: Set DEBUG=1 for full stack trace, or check the method implementation.");
1273
+ this.exitWithError(errorMessage);
1274
+ }
1275
+ }
1276
+ /**
1277
+ * Get or create collection for an object
1278
+ * Uses database configuration from smrt.config.js or defaults to :memory:
1279
+ */
1280
+ async getCollection(objectName) {
1281
+ if (!this.collections.has(objectName)) {
1282
+ const classInfo = ObjectRegistry.getClass(objectName);
1283
+ if (!classInfo || !classInfo.collectionConstructor) {
1284
+ const availableObjects = Array.from(ObjectRegistry.getAllClasses().values()).map((info) => info.name);
1285
+ throw new Error(`Object '${objectName}' not found or has no collection constructor.\n\nAvailable objects:\n ${availableObjects.join("\n ")}\n\nTroubleshooting:\n1. If from external package, ensure it's installed:\n npm install <package-name>\n\n2. Rebuild your project to regenerate manifest:\n npm run build\n\n3. Check .smrt/manifest.json contains the object\n\n4. Verify package exports classes correctly\n5. Run with verbose mode for more details:\n SMRT_CLI_VERBOSE=true npx smrt ${objectName}:list\n`);
1286
+ }
1287
+ let db = this.context.db;
1288
+ if (!db) {
1289
+ const { getPackageConfig } = await import("@happyvertical/smrt-config");
1290
+ const { DEFAULT_CLI_CONFIG } = await import("./config-BwrFRL8L.js");
1291
+ const config = getPackageConfig("cli", DEFAULT_CLI_CONFIG);
1292
+ const { getDatabase } = await import("@happyvertical/sql");
1293
+ db = await getDatabase({
1294
+ type: config.database.type,
1295
+ url: config.database.url
1296
+ });
1297
+ if (config.verbose) console.log(`[CLI] Using database: ${config.database.url}`);
1298
+ }
1299
+ const collection = new classInfo.collectionConstructor({
1300
+ ai: this.context.ai,
1301
+ db
1302
+ });
1303
+ await collection.initialize();
1304
+ this.collections.set(objectName, collection);
1305
+ }
1306
+ const collection = this.collections.get(objectName);
1307
+ if (!collection) throw new Error(`Collection ${objectName} not found`);
1308
+ return collection;
1309
+ }
1310
+ /**
1311
+ * Interactive field prompts
1312
+ */
1313
+ async promptForFields(objectName, current) {
1314
+ const fields = ObjectRegistry.getFields(objectName);
1315
+ const result = {};
1316
+ for (const [fieldName, field] of fields) {
1317
+ const currentValue = current[fieldName];
1318
+ let message = `${fieldName}`;
1319
+ if (field.options?.description) message += ` (${field.options.description})`;
1320
+ if (currentValue !== void 0) message += ` [${currentValue}]`;
1321
+ message += ": ";
1322
+ if (field.type === "boolean") result[fieldName] = await this.confirm(message);
1323
+ else {
1324
+ const input = await this.prompt(message);
1325
+ if (input.trim()) result[fieldName] = this.parseFieldValue(input);
1326
+ else if (currentValue !== void 0) result[fieldName] = currentValue;
1327
+ }
1328
+ }
1329
+ return result;
1330
+ }
1331
+ /**
1332
+ * Parse field value from string
1333
+ */
1334
+ parseFieldValue(value) {
1335
+ try {
1336
+ return JSON.parse(value);
1337
+ } catch {
1338
+ return value;
1339
+ }
1340
+ }
1341
+ /**
1342
+ * Display results as table
1343
+ */
1344
+ displayTable(results, objectName) {
1345
+ if (results.length === 0) {
1346
+ console.log(`No ${objectName} objects found`);
1347
+ return;
1348
+ }
1349
+ const keys = [
1350
+ "id",
1351
+ "name",
1352
+ "slug",
1353
+ "created_at"
1354
+ ];
1355
+ const rows = results.map((item) => {
1356
+ const record = item;
1357
+ return keys.map((key) => String(record[key] || "").substring(0, 30));
1358
+ });
1359
+ console.log();
1360
+ console.log(keys.join(" "));
1361
+ console.log("-".repeat(80));
1362
+ rows.forEach((row) => {
1363
+ console.log(row.join(" "));
1364
+ });
1365
+ console.log();
1366
+ }
1367
+ /**
1368
+ * Convert object to YAML-like string
1369
+ */
1370
+ toYamlString(obj, indent = 0) {
1371
+ const spaces = " ".repeat(indent);
1372
+ let result = "";
1373
+ for (const [key, value] of Object.entries(obj)) if (value === null || value === void 0) result += `${spaces}${key}: null\n`;
1374
+ else if (typeof value === "object" && !Array.isArray(value)) result += `${spaces}${key}:\n${this.toYamlString(value, indent + 1)}`;
1375
+ else if (Array.isArray(value)) {
1376
+ result += `${spaces}${key}:\n`;
1377
+ value.forEach((item) => {
1378
+ result += `${spaces} - ${item}\n`;
1379
+ });
1380
+ } else result += `${spaces}${key}: ${value}\n`;
1381
+ return result;
1382
+ }
1383
+ };
1743
1384
  async function main() {
1744
- const args = process.argv.slice(2);
1745
- const timingEnabled = args.includes("--timing");
1746
- const timing = {};
1747
- const startTime = timingEnabled ? performance.now() : 0;
1748
- {
1749
- const fs = await import("node:fs");
1750
- const path = await import("node:path");
1751
- const configNames = [
1752
- "smrt.config.js",
1753
- "smrt.config.mjs",
1754
- "smrt.config.cjs",
1755
- "smrt.config.json"
1756
- ];
1757
- let dir = process.cwd();
1758
- const { root } = path.parse(dir);
1759
- while (dir !== root) {
1760
- if (configNames.some((name) => fs.existsSync(path.join(dir, name)))) {
1761
- try {
1762
- const { loadEnvFile } = await import("node:process");
1763
- loadEnvFile(path.join(dir, ".env"));
1764
- } catch {
1765
- }
1766
- break;
1767
- }
1768
- dir = path.dirname(dir);
1769
- }
1770
- }
1771
- const configStart = timingEnabled ? performance.now() : 0;
1772
- const { loadConfig } = await import("@happyvertical/smrt-config");
1773
- await loadConfig({ cache: true });
1774
- if (timingEnabled) {
1775
- timing.config = performance.now() - configStart;
1776
- }
1777
- const config = {
1778
- name: "smrt",
1779
- version: CLI_VERSION,
1780
- description: "Admin CLI for smrt objects",
1781
- prompt: !process.env.CI,
1782
- // Disable prompts in CI
1783
- colors: !process.env.NO_COLOR && process.stdout.isTTY
1784
- };
1785
- const context = {
1786
- // db and ai can be configured via environment or initialized here
1787
- timing: timingEnabled ? timing : void 0
1788
- };
1789
- const cli = new CLIGenerator(config, context);
1790
- const handler = cli.generateHandler();
1791
- const filteredArgs = args.filter((arg) => arg !== "--timing");
1792
- try {
1793
- await handler(filteredArgs);
1794
- } catch (error) {
1795
- console.error(
1796
- "CLI Error:",
1797
- error instanceof Error ? error.message : "Unknown error"
1798
- );
1799
- process.exit(1);
1800
- }
1801
- if (timingEnabled) {
1802
- timing.total = performance.now() - startTime;
1803
- console.log("\n⏱ Startup Timing:");
1804
- console.log(` Config load: ${timing.config?.toFixed(0) ?? "N/A"}ms`);
1805
- console.log(
1806
- ` Class loading: ${timing.classLoading?.toFixed(0) ?? "N/A"}ms`
1807
- );
1808
- console.log(
1809
- ` Command gen: ${timing.commandGen?.toFixed(0) ?? "N/A"}ms`
1810
- );
1811
- console.log(` Total startup: ${timing.total.toFixed(0)}ms`);
1812
- }
1385
+ const args = process.argv.slice(2);
1386
+ const timingEnabled = args.includes("--timing");
1387
+ const timing = {};
1388
+ const startTime = timingEnabled ? performance.now() : 0;
1389
+ {
1390
+ const fs = await import("node:fs");
1391
+ const path = await import("node:path");
1392
+ const configNames = [
1393
+ "smrt.config.js",
1394
+ "smrt.config.mjs",
1395
+ "smrt.config.cjs",
1396
+ "smrt.config.json"
1397
+ ];
1398
+ let dir = process.cwd();
1399
+ const { root } = path.parse(dir);
1400
+ while (dir !== root) {
1401
+ if (configNames.some((name) => fs.existsSync(path.join(dir, name)))) {
1402
+ try {
1403
+ const { loadEnvFile } = await import("node:process");
1404
+ loadEnvFile(path.join(dir, ".env"));
1405
+ } catch {}
1406
+ break;
1407
+ }
1408
+ dir = path.dirname(dir);
1409
+ }
1410
+ }
1411
+ const configStart = timingEnabled ? performance.now() : 0;
1412
+ const { loadConfig } = await import("@happyvertical/smrt-config");
1413
+ await loadConfig({ cache: true });
1414
+ if (timingEnabled) timing.config = performance.now() - configStart;
1415
+ const handler = new CLIGenerator({
1416
+ name: "smrt",
1417
+ version: CLI_VERSION,
1418
+ description: "Admin CLI for smrt objects",
1419
+ prompt: !process.env.CI,
1420
+ colors: !process.env.NO_COLOR && process.stdout.isTTY
1421
+ }, { timing: timingEnabled ? timing : void 0 }).generateHandler();
1422
+ const filteredArgs = args.filter((arg) => arg !== "--timing");
1423
+ try {
1424
+ await handler(filteredArgs);
1425
+ } catch (error) {
1426
+ console.error("CLI Error:", error instanceof Error ? error.message : "Unknown error");
1427
+ process.exit(1);
1428
+ }
1429
+ if (timingEnabled) {
1430
+ timing.total = performance.now() - startTime;
1431
+ console.log("\n⏱ Startup Timing:");
1432
+ console.log(` Config load: ${timing.config?.toFixed(0) ?? "N/A"}ms`);
1433
+ console.log(` Class loading: ${timing.classLoading?.toFixed(0) ?? "N/A"}ms`);
1434
+ console.log(` Command gen: ${timing.commandGen?.toFixed(0) ?? "N/A"}ms`);
1435
+ console.log(` Total startup: ${timing.total.toFixed(0)}ms`);
1436
+ }
1813
1437
  }
1814
- const cliEntryGlobal = globalThis;
1438
+ //#endregion
1439
+ //#region src/index.ts
1440
+ /**
1441
+ * @happyvertical/smrt-cli
1442
+ *
1443
+ * Developer CLI for SMRT framework
1444
+ * Provides introspection, testing, and project management tools
1445
+ */
1446
+ var cliEntryGlobal = globalThis;
1815
1447
  if (!cliEntryGlobal.__SMRT_CLI_ENTRY_RUNNING__) {
1816
- cliEntryGlobal.__SMRT_CLI_ENTRY_RUNNING__ = true;
1817
- main().catch((error) => {
1818
- console.error(
1819
- "CLI Error:",
1820
- error instanceof Error ? error.message : "Unknown error"
1821
- );
1822
- process.exit(1);
1823
- });
1448
+ cliEntryGlobal.__SMRT_CLI_ENTRY_RUNNING__ = true;
1449
+ main().catch((error) => {
1450
+ console.error("CLI Error:", error instanceof Error ? error.message : "Unknown error");
1451
+ process.exit(1);
1452
+ });
1824
1453
  }
1454
+ //#endregion
1455
+ export {};