owosk 0.1.2 → 0.1.4

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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +414 -167
  3. package/package.json +5 -4
package/README.md CHANGED
@@ -20,7 +20,7 @@ npx owo --help
20
20
 
21
21
  ### `init`
22
22
 
23
- Initialize a new Owostack project with a default `owo.config.ts`.
23
+ Initialize a new Owostack project with a default configuration file (`owo.config.ts` or `owo.config.js`).
24
24
 
25
25
  ```bash
26
26
  npx owo init
@@ -52,7 +52,7 @@ npx owo diff
52
52
 
53
53
  ### `validate`
54
54
 
55
- Check your local `owo.config.ts` for errors without applying changes.
55
+ Check your local configuration for errors without applying changes.
56
56
 
57
57
  ```bash
58
58
  npx owo validate
package/dist/index.js CHANGED
@@ -46,17 +46,41 @@ function getApiKey(cliKey) {
46
46
  }
47
47
 
48
48
  // src/lib/loader.ts
49
- import { resolve, isAbsolute } from "path";
50
- import { pathToFileURL } from "url";
49
+ import { resolve, isAbsolute, extname } from "path";
51
50
  import { existsSync as existsSync2 } from "fs";
51
+ import { createJiti } from "jiti";
52
+ var jiti = createJiti(import.meta.url);
53
+ var DEFAULT_CONFIG_NAMES = [
54
+ "owo.config.ts",
55
+ "owo.config.js",
56
+ "owo.config.mjs",
57
+ "owo.config.cjs",
58
+ "owo.config.mts",
59
+ "owo.config.cts"
60
+ ];
52
61
  function resolveConfigPath(configPath) {
53
- return isAbsolute(configPath) ? configPath : resolve(process.cwd(), configPath);
62
+ if (configPath) {
63
+ const fullPath = isAbsolute(configPath) ? configPath : resolve(process.cwd(), configPath);
64
+ if (existsSync2(fullPath)) return fullPath;
65
+ return null;
66
+ }
67
+ for (const name of DEFAULT_CONFIG_NAMES) {
68
+ const fullPath = resolve(process.cwd(), name);
69
+ if (existsSync2(fullPath)) return fullPath;
70
+ }
71
+ return null;
54
72
  }
55
73
  async function loadOwostackFromConfig(fullPath) {
56
- let configModule;
57
74
  try {
58
- const fileUrl = pathToFileURL(fullPath).href;
59
- configModule = await import(fileUrl);
75
+ const configModule = await jiti.import(fullPath);
76
+ const instance = configModule.default || configModule.owo || configModule;
77
+ if (instance && typeof instance.sync === "function") {
78
+ return instance;
79
+ }
80
+ if (typeof configModule.sync === "function") {
81
+ return configModule;
82
+ }
83
+ return null;
60
84
  } catch (e) {
61
85
  console.error(`
62
86
  \u274C Failed to load config from ${fullPath}`);
@@ -65,23 +89,36 @@ async function loadOwostackFromConfig(fullPath) {
65
89
  console.error(
66
90
  ` Make sure the file exports an Owostack instance as default or named 'owo'.`
67
91
  );
68
- console.error(` Example owo.config.ts:
92
+ const ext = extname(fullPath);
93
+ const isTs = ext === ".ts" || ext === ".mts" || ext === ".cts";
94
+ if (isTs) {
95
+ console.error(` Example owo.config.ts:
69
96
  `);
70
- console.error(
71
- ` import { Owostack, metered, boolean, plan } from "owostack";`
72
- );
73
- console.error(
74
- ` export default new Owostack({ secretKey: "...", catalog: [...] });
97
+ console.error(
98
+ ` import { Owostack, metered, boolean, plan } from "owostack";`
99
+ );
100
+ console.error(
101
+ ` export default new Owostack({ secretKey: "...", catalog: [...] });
75
102
  `
76
- );
103
+ );
104
+ } else {
105
+ console.error(` Example owo.config.js:
106
+ `);
107
+ console.error(
108
+ ` const { Owostack, metered, boolean, plan } = require("owostack");`
109
+ );
110
+ console.error(
111
+ ` module.exports = new Owostack({ secretKey: "...", catalog: [...] });
112
+ `
113
+ );
114
+ }
77
115
  process.exit(1);
78
116
  }
79
- return configModule.default || configModule.owo;
80
117
  }
81
118
  async function loadConfigSettings(configPath) {
82
119
  try {
83
120
  const fullPath = resolveConfigPath(configPath);
84
- if (!existsSync2(fullPath)) return {};
121
+ if (!fullPath) return {};
85
122
  const owo = await loadOwostackFromConfig(fullPath);
86
123
  if (!owo || !owo._config) return {};
87
124
  return {
@@ -90,7 +127,7 @@ async function loadConfigSettings(configPath) {
90
127
  filters: owo._config.filters,
91
128
  connect: owo._config.connect
92
129
  };
93
- } catch {
130
+ } catch (e) {
94
131
  return {};
95
132
  }
96
133
  }
@@ -100,9 +137,34 @@ async function runSyncSingle(options) {
100
137
  const { configPath, dryRun, apiUrl } = options;
101
138
  const apiKey = getApiKey(options.apiKey);
102
139
  const fullPath = resolveConfigPath(configPath);
140
+ if (!fullPath) {
141
+ p.log.error(
142
+ pc.red(
143
+ `Configuration file not found.${configPath ? ` looked at ${configPath}` : " searched defaults."}`
144
+ )
145
+ );
146
+ process.exit(1);
147
+ }
103
148
  const s = p.spinner();
104
- s.start(`Loading ${pc.cyan(configPath)}`);
105
- const owo = await loadOwostackFromConfig(fullPath);
149
+ s.start(`Loading ${pc.cyan(fullPath)}`);
150
+ let owo;
151
+ try {
152
+ owo = await loadOwostackFromConfig(fullPath);
153
+ } catch (e) {
154
+ s.stop(pc.red("Failed to load configuration"));
155
+ p.log.error(pc.red(`Error: ${e.message}`));
156
+ p.note(
157
+ `import { Owostack, metered, boolean, plan } from "owostack";
158
+ export default new Owostack({ secretKey: "...", catalog: [...] });`,
159
+ "Example owo.config.ts"
160
+ );
161
+ p.log.info(
162
+ pc.dim(
163
+ "Make sure 'owostack' is installed in your project: 'npm install owostack'"
164
+ )
165
+ );
166
+ process.exit(1);
167
+ }
106
168
  if (!owo || typeof owo.sync !== "function") {
107
169
  s.stop(pc.red("Invalid configuration"));
108
170
  p.log.error("Config file must export an Owostack instance.");
@@ -223,11 +285,13 @@ async function runSync(options) {
223
285
 
224
286
  // src/commands/pull.ts
225
287
  import * as p2 from "@clack/prompts";
226
- import pc2 from "picocolors";
227
- import { existsSync as existsSync3 } from "fs";
288
+ import pc3 from "picocolors";
289
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
228
290
  import { writeFile as writeFile2 } from "fs/promises";
291
+ import { join as join2, resolve as resolve2, extname as extname2, isAbsolute as isAbsolute2 } from "path";
229
292
 
230
293
  // src/lib/api.ts
294
+ import pc2 from "picocolors";
231
295
  async function fetchPlans(options) {
232
296
  if (!options.apiKey) {
233
297
  console.error(
@@ -242,19 +306,40 @@ async function fetchPlans(options) {
242
306
  if (options.interval) url.searchParams.set("interval", options.interval);
243
307
  if (options.currency) url.searchParams.set("currency", options.currency);
244
308
  if (options.includeInactive) url.searchParams.set("includeInactive", "true");
245
- const response = await fetch(url.toString(), {
246
- method: "GET",
247
- headers: { Authorization: `Bearer ${options.apiKey}` }
248
- });
249
- const data = await response.json();
250
- if (!response.ok || !data?.success) {
251
- const message = data?.error || data?.message || "Request failed";
252
- console.error(`
309
+ try {
310
+ const response = await fetch(url.toString(), {
311
+ method: "GET",
312
+ headers: { Authorization: `Bearer ${options.apiKey}` }
313
+ });
314
+ const data = await response.json();
315
+ if (!response.ok || !data?.success) {
316
+ const message = data?.error || data?.message || "Request failed";
317
+ console.error(`
253
318
  \u274C Failed to fetch plans: ${message}
254
319
  `);
320
+ process.exit(1);
321
+ }
322
+ return data?.plans || [];
323
+ } catch (error) {
324
+ if (error.name === "TypeError" && error.message.includes("fetch failed")) {
325
+ console.error(
326
+ `
327
+ \u274C Connection failed: Could not reach the API at ${pc2.cyan(options.apiUrl)}`
328
+ );
329
+ console.error(
330
+ ` Please check your internet connection or ensure the API is running.`
331
+ );
332
+ console.error(
333
+ ` You can override the API URL by setting the ${pc2.bold("OWOSTACK_API_URL")} environment variable.
334
+ `
335
+ );
336
+ } else {
337
+ console.error(`
338
+ \u274C Unexpected error: ${error.message}
339
+ `);
340
+ }
255
341
  process.exit(1);
256
342
  }
257
- return data?.plans || [];
258
343
  }
259
344
  async function fetchCreditSystems(apiKey, apiUrl) {
260
345
  if (!apiKey) {
@@ -329,7 +414,9 @@ function slugToIdentifier(slug, used) {
329
414
  used.add(candidate);
330
415
  return candidate;
331
416
  }
332
- function generateConfig(plans, creditSystems = [], defaultProvider) {
417
+ function generateConfig(plans, creditSystems = [], defaultProvider, format = "ts") {
418
+ const isTs = format === "ts";
419
+ const isCjs = format === "cjs";
333
420
  const creditSystemSlugs = new Set(creditSystems.map((cs) => cs.slug));
334
421
  const creditSystemBySlug = new Map(creditSystems.map((cs) => [cs.slug, cs]));
335
422
  const featuresBySlug = /* @__PURE__ */ new Map();
@@ -340,7 +427,8 @@ function generateConfig(plans, creditSystems = [], defaultProvider) {
340
427
  featuresBySlug.set(f.slug, {
341
428
  slug: f.slug,
342
429
  name: f.name,
343
- type: f.type || "metered"
430
+ type: f.type || "metered",
431
+ meterType: f.meterType
344
432
  });
345
433
  }
346
434
  }
@@ -363,14 +451,14 @@ function generateConfig(plans, creditSystems = [], defaultProvider) {
363
451
  const varName = slugToIdentifier(feature.slug, usedNames);
364
452
  featureVars.set(feature.slug, varName);
365
453
  const nameArg = feature.name ? `, { name: ${JSON.stringify(feature.name)} }` : "";
366
- if (feature.type === "boolean") {
367
- featureLines.push(
368
- `export const ${varName} = boolean(${JSON.stringify(feature.slug)}${nameArg});`
369
- );
454
+ const isEntity = feature.meterType === "non_consumable";
455
+ const builder = feature.type === "boolean" ? "boolean" : isEntity ? "entity" : "metered";
456
+ const decl = `${builder}(${JSON.stringify(feature.slug)}${nameArg})`;
457
+ if (isCjs) {
458
+ featureLines.push(`const ${varName} = ${decl};`);
459
+ featureLines.push(`exports.${varName} = ${varName};`);
370
460
  } else {
371
- featureLines.push(
372
- `export const ${varName} = metered(${JSON.stringify(feature.slug)}${nameArg});`
373
- );
461
+ featureLines.push(`export const ${varName} = ${decl};`);
374
462
  }
375
463
  }
376
464
  const creditSystemLines = [];
@@ -389,9 +477,13 @@ function generateConfig(plans, creditSystems = [], defaultProvider) {
389
477
  descArg,
390
478
  `features: [${featureEntries.join(", ")}]`
391
479
  ].filter(Boolean);
392
- creditSystemLines.push(
393
- `export const ${varName} = creditSystem(${JSON.stringify(cs.slug)}, { ${optsParts.join(", ")} });`
394
- );
480
+ const decl = `creditSystem(${JSON.stringify(cs.slug)}, { ${optsParts.join(", ")} })`;
481
+ if (isCjs) {
482
+ creditSystemLines.push(`const ${varName} = ${decl};`);
483
+ creditSystemLines.push(`exports.${varName} = ${varName};`);
484
+ } else {
485
+ creditSystemLines.push(`export const ${varName} = ${decl};`);
486
+ }
395
487
  }
396
488
  const planLines = [];
397
489
  for (const plan of plans) {
@@ -447,9 +539,13 @@ function generateConfig(plans, creditSystems = [], defaultProvider) {
447
539
  featureEntries.push(`${varName}.config(${JSON.stringify(config2)})`);
448
540
  continue;
449
541
  }
542
+ const isEntityFeature = globalFeature?.meterType === "non_consumable";
450
543
  const config = {};
451
544
  if (pf.limit !== void 0) config.limit = pf.limit;
452
- config.reset = pf.resetInterval || pf.reset || "monthly";
545
+ if (!isEntityFeature) {
546
+ const reset = pf.resetInterval || pf.reset || "monthly";
547
+ if (reset !== "none") config.reset = reset;
548
+ }
453
549
  if (pf.overage) config.overage = pf.overage;
454
550
  if (pf.overagePrice !== void 0) config.overagePrice = pf.overagePrice;
455
551
  const configKeys = Object.keys(config);
@@ -475,46 +571,91 @@ function generateConfig(plans, creditSystems = [], defaultProvider) {
475
571
  );
476
572
  }
477
573
  const hasCreditSystems = creditSystemLines.length > 0;
574
+ const hasEntities = Array.from(featuresBySlug.values()).some(
575
+ (f) => f.meterType === "non_consumable"
576
+ );
478
577
  const providerLine = defaultProvider ? ` provider: ${JSON.stringify(defaultProvider)},
479
578
  ` : "";
579
+ const importParts = ["Owostack", "metered", "boolean"];
580
+ if (hasEntities) importParts.push("entity");
581
+ importParts.push("creditSystem", "plan");
582
+ const imports = isCjs ? `const { ${importParts.join(", ")} } = require("owostack");` : `import { ${importParts.join(", ")} } from "owostack";`;
583
+ const tsCheck = !isTs ? `// @ts-check` : "";
584
+ const jsDoc = !isTs ? `/** @type {import('owostack').Owostack} */` : "";
585
+ const owoDecl = isCjs ? "exports.owo =" : "export const owo =";
586
+ const secretKey = isTs ? "process.env.OWOSTACK_SECRET_KEY!" : "process.env.OWOSTACK_SECRET_KEY";
480
587
  return [
481
- `import { Owostack, metered, boolean, creditSystem, plan } from "owostack";`,
588
+ tsCheck,
589
+ imports,
482
590
  ``,
483
591
  ...featureLines,
484
592
  ...hasCreditSystems ? ["", ...creditSystemLines] : [],
485
593
  ``,
486
- `export const owo = new Owostack({`,
487
- ` secretKey: process.env.OWOSTACK_SECRET_KEY!,`,
594
+ jsDoc,
595
+ `${owoDecl} new Owostack({`,
596
+ ` secretKey: ${secretKey},`,
488
597
  providerLine,
489
598
  ` catalog: [`,
490
599
  ` ${planLines.join(",\n ")}`,
491
600
  ` ],`,
492
601
  `});`,
493
602
  ``
494
- ].join("\n");
603
+ ].filter(Boolean).join("\n");
495
604
  }
496
605
 
497
606
  // src/commands/pull.ts
607
+ function getProjectInfo() {
608
+ const cwd = process.cwd();
609
+ let isEsm = false;
610
+ try {
611
+ const pkg = JSON.parse(readFileSync2(join2(cwd, "package.json"), "utf8"));
612
+ isEsm = pkg.type === "module";
613
+ } catch {
614
+ }
615
+ return { isEsm };
616
+ }
617
+ function determineFormat(fullPath) {
618
+ const ext = extname2(fullPath);
619
+ const { isEsm } = getProjectInfo();
620
+ if (ext === ".ts" || ext === ".mts" || ext === ".cts") return "ts";
621
+ if (ext === ".mjs") return "esm";
622
+ if (ext === ".cjs") return "cjs";
623
+ if (ext === ".js") return isEsm ? "esm" : "cjs";
624
+ return "ts";
625
+ }
498
626
  async function runPull(options) {
499
- p2.intro(pc2.bgYellow(pc2.black(" pull ")));
500
- const fullPath = resolveConfigPath(options.config);
627
+ p2.intro(pc3.bgYellow(pc3.black(" pull ")));
628
+ let fullPath;
629
+ if (options.config) {
630
+ fullPath = isAbsolute2(options.config) ? options.config : resolve2(process.cwd(), options.config);
631
+ } else {
632
+ const resolved = resolveConfigPath();
633
+ if (!resolved) {
634
+ p2.log.error(
635
+ pc3.red("No configuration file found. Run 'owostack init' first.")
636
+ );
637
+ process.exit(1);
638
+ }
639
+ fullPath = resolved;
640
+ }
501
641
  const apiKey = getApiKey(options.key);
502
642
  const configSettings = await loadConfigSettings(options.config);
503
643
  const baseUrl = getApiUrl(configSettings.apiUrl);
504
644
  const filters = configSettings.filters || {};
645
+ const format = determineFormat(fullPath);
505
646
  const s = p2.spinner();
506
647
  if (options.prod) {
507
- p2.log.step(pc2.magenta("Production Mode: Fetching both environments"));
648
+ p2.log.step(pc3.magenta("Production Mode: Fetching both environments"));
508
649
  const testUrl = getTestApiUrl(configSettings.environments?.test);
509
650
  const liveUrl = getApiUrl(configSettings.environments?.live);
510
- s.start(`Fetching from ${pc2.dim("test")}...`);
651
+ s.start(`Fetching from ${pc3.dim("test")}...`);
511
652
  const testPlans = await fetchPlans({
512
653
  apiKey,
513
654
  apiUrl: `${testUrl}/api/v1`,
514
655
  ...filters
515
656
  });
516
657
  s.stop(`Fetched ${testPlans.length} plans from test`);
517
- s.start(`Fetching from ${pc2.dim("live")}...`);
658
+ s.start(`Fetching from ${pc3.dim("live")}...`);
518
659
  const livePlans = await fetchPlans({
519
660
  apiKey,
520
661
  apiUrl: `${liveUrl}/api/v1`,
@@ -531,22 +672,23 @@ async function runPull(options) {
531
672
  const configContent = generateConfig(
532
673
  livePlans,
533
674
  creditSystems,
534
- defaultProvider
675
+ defaultProvider,
676
+ format
535
677
  );
536
678
  if (options.dryRun) {
537
679
  p2.note(configContent, "Generated Config (Dry Run)");
538
- p2.outro(pc2.yellow("Dry run complete. No changes made."));
680
+ p2.outro(pc3.yellow("Dry run complete. No changes made."));
539
681
  return;
540
682
  }
541
683
  if (existsSync3(fullPath) && !options.force) {
542
- p2.log.error(pc2.red(`Config file already exists at ${fullPath}`));
543
- p2.log.info(pc2.dim("Use --force to overwrite."));
684
+ p2.log.error(pc3.red(`Config file already exists at ${fullPath}`));
685
+ p2.log.info(pc3.dim("Use --force to overwrite."));
544
686
  process.exit(1);
545
687
  }
546
688
  await writeFile2(fullPath, configContent, "utf8");
547
- p2.log.success(pc2.green(`Wrote configuration to ${fullPath}`));
689
+ p2.log.success(pc3.green(`Wrote configuration to ${fullPath}`));
548
690
  } else {
549
- s.start(`Fetching plans from ${pc2.dim(baseUrl)}...`);
691
+ s.start(`Fetching plans from ${pc3.dim(baseUrl)}...`);
550
692
  const plans = await fetchPlans({
551
693
  apiKey,
552
694
  apiUrl: `${baseUrl}/api/v1`,
@@ -560,10 +702,15 @@ async function runPull(options) {
560
702
  plans.map((p9) => p9.provider).filter(Boolean)
561
703
  );
562
704
  const defaultProvider = providers.size === 1 ? Array.from(providers)[0] : void 0;
563
- const configContent = generateConfig(plans, creditSystems, defaultProvider);
705
+ const configContent = generateConfig(
706
+ plans,
707
+ creditSystems,
708
+ defaultProvider,
709
+ format
710
+ );
564
711
  if (options.dryRun) {
565
712
  p2.note(configContent, "Generated Config (Dry Run)");
566
- p2.outro(pc2.yellow("Dry run complete. No changes made."));
713
+ p2.outro(pc3.yellow("Dry run complete. No changes made."));
567
714
  return;
568
715
  }
569
716
  if (existsSync3(fullPath) && !options.force) {
@@ -572,22 +719,22 @@ async function runPull(options) {
572
719
  initialValue: false
573
720
  });
574
721
  if (p2.isCancel(confirm3) || !confirm3) {
575
- p2.outro(pc2.yellow("Operation cancelled"));
722
+ p2.outro(pc3.yellow("Operation cancelled"));
576
723
  process.exit(0);
577
724
  }
578
725
  }
579
726
  await writeFile2(fullPath, configContent, "utf8");
580
- p2.log.success(pc2.green(`Wrote configuration to ${fullPath}`));
727
+ p2.log.success(pc3.green(`Wrote configuration to ${fullPath}`));
581
728
  }
582
- p2.outro(pc2.green("Pull complete! \u2728"));
729
+ p2.outro(pc3.green("Pull complete! \u2728"));
583
730
  }
584
731
 
585
732
  // src/commands/diff.ts
586
733
  import * as p4 from "@clack/prompts";
587
- import pc4 from "picocolors";
734
+ import pc5 from "picocolors";
588
735
 
589
736
  // src/lib/diff.ts
590
- import pc3 from "picocolors";
737
+ import pc4 from "picocolors";
591
738
  import * as p3 from "@clack/prompts";
592
739
  function normalizeFeature(pf) {
593
740
  return {
@@ -647,7 +794,7 @@ function diffPlans(localPlans, remotePlans) {
647
794
  const localVal = JSON.stringify(local[field]);
648
795
  const remoteVal = JSON.stringify(remote[field]);
649
796
  details.push(
650
- `${String(field)}: ${pc3.green(localVal)} \u2192 ${pc3.red(remoteVal)}`
797
+ `${String(field)}: ${pc4.green(localVal)} \u2192 ${pc4.red(remoteVal)}`
651
798
  );
652
799
  }
653
800
  }
@@ -657,20 +804,20 @@ function diffPlans(localPlans, remotePlans) {
657
804
  );
658
805
  for (const fslug of localFeatures.keys()) {
659
806
  if (!remoteFeatures.has(fslug)) {
660
- details.push(`feature ${fslug}: ${pc3.green("[local only]")}`);
807
+ details.push(`feature ${fslug}: ${pc4.green("[local only]")}`);
661
808
  continue;
662
809
  }
663
810
  const lf = localFeatures.get(fslug);
664
811
  const rf = remoteFeatures.get(fslug);
665
812
  if (JSON.stringify(lf) !== JSON.stringify(rf)) {
666
813
  details.push(`feature ${fslug}:`);
667
- details.push(` ${pc3.green(JSON.stringify(lf))}`);
668
- details.push(` ${pc3.red(JSON.stringify(rf))}`);
814
+ details.push(` ${pc4.green(JSON.stringify(lf))}`);
815
+ details.push(` ${pc4.red(JSON.stringify(rf))}`);
669
816
  }
670
817
  }
671
818
  for (const fslug of remoteFeatures.keys()) {
672
819
  if (!localFeatures.has(fslug)) {
673
- details.push(`feature ${fslug}: ${pc3.red("[remote only]")}`);
820
+ details.push(`feature ${fslug}: ${pc4.red("[remote only]")}`);
674
821
  }
675
822
  }
676
823
  if (details.length > 0) {
@@ -681,18 +828,18 @@ function diffPlans(localPlans, remotePlans) {
681
828
  }
682
829
  function printDiff(diff) {
683
830
  if (diff.onlyLocal.length === 0 && diff.onlyRemote.length === 0 && diff.changed.length === 0) {
684
- p3.log.success(pc3.green("No differences found."));
831
+ p3.log.success(pc4.green("No differences found."));
685
832
  return;
686
833
  }
687
834
  if (diff.onlyLocal.length > 0) {
688
835
  p3.note(
689
- diff.onlyLocal.map((slug) => `${pc3.green("+")} ${slug}`).join("\n"),
836
+ diff.onlyLocal.map((slug) => `${pc4.green("+")} ${slug}`).join("\n"),
690
837
  "Only in Local"
691
838
  );
692
839
  }
693
840
  if (diff.onlyRemote.length > 0) {
694
841
  p3.note(
695
- diff.onlyRemote.map((slug) => `${pc3.red("-")} ${slug}`).join("\n"),
842
+ diff.onlyRemote.map((slug) => `${pc4.red("-")} ${slug}`).join("\n"),
696
843
  "Only in Remote"
697
844
  );
698
845
  }
@@ -700,7 +847,7 @@ function printDiff(diff) {
700
847
  let changedText = "";
701
848
  for (const item of diff.changed) {
702
849
  changedText += `
703
- ${pc3.bold(item.slug)}
850
+ ${pc4.bold(item.slug)}
704
851
  `;
705
852
  for (const line of item.details) {
706
853
  changedText += ` ${line}
@@ -710,47 +857,75 @@ ${pc3.bold(item.slug)}
710
857
  p3.note(changedText.trim(), "Changed Plans");
711
858
  }
712
859
  const summary = [
713
- diff.onlyLocal.length > 0 ? `${pc3.green(diff.onlyLocal.length.toString())} added` : "",
714
- diff.onlyRemote.length > 0 ? `${pc3.red(diff.onlyRemote.length.toString())} removed` : "",
715
- diff.changed.length > 0 ? `${pc3.yellow(diff.changed.length.toString())} changed` : ""
860
+ diff.onlyLocal.length > 0 ? `${pc4.green(diff.onlyLocal.length.toString())} added` : "",
861
+ diff.onlyRemote.length > 0 ? `${pc4.red(diff.onlyRemote.length.toString())} removed` : "",
862
+ diff.changed.length > 0 ? `${pc4.yellow(diff.changed.length.toString())} changed` : ""
716
863
  ].filter(Boolean).join(" ");
717
864
  p3.log.info(summary);
718
865
  }
719
866
 
720
867
  // src/commands/diff.ts
721
868
  async function runDiff(options) {
722
- p4.intro(pc4.bgYellow(pc4.black(" diff ")));
869
+ p4.intro(pc5.bgYellow(pc5.black(" diff ")));
723
870
  const fullPath = resolveConfigPath(options.config);
871
+ if (!fullPath) {
872
+ p4.log.error(pc5.red("No configuration file found."));
873
+ process.exit(1);
874
+ }
724
875
  const apiKey = getApiKey(options.key);
725
876
  const configSettings = await loadConfigSettings(options.config);
726
877
  const baseUrl = getApiUrl(configSettings.apiUrl);
727
878
  const s = p4.spinner();
728
879
  if (options.prod) {
729
- p4.log.step(pc4.magenta("Production Mode: Comparing both environments"));
880
+ p4.log.step(pc5.magenta("Production Mode: Comparing both environments"));
730
881
  const testUrl = getTestApiUrl(configSettings.environments?.test);
731
882
  const liveUrl = getApiUrl(configSettings.environments?.live);
732
883
  s.start("Loading local configuration...");
733
- const owo = await loadOwostackFromConfig(fullPath);
884
+ let owo;
885
+ try {
886
+ owo = await loadOwostackFromConfig(fullPath);
887
+ } catch (e) {
888
+ s.stop(pc5.red("Failed to load configuration"));
889
+ p4.log.error(pc5.red(`Error: ${e.message}`));
890
+ p4.log.info(
891
+ pc5.dim(
892
+ "Make sure 'owostack' is installed in your project: 'npm install owostack'"
893
+ )
894
+ );
895
+ process.exit(1);
896
+ }
734
897
  s.stop("Configuration loaded");
735
898
  const { buildSyncPayload } = await import("owostack").catch(() => ({
736
899
  buildSyncPayload: null
737
900
  }));
738
901
  const localPayload = buildSyncPayload(owo._config.catalog);
739
- p4.log.step(pc4.cyan(`Comparing with TEST: ${testUrl}`));
902
+ p4.log.step(pc5.cyan(`Comparing with TEST: ${testUrl}`));
740
903
  const testPlans = await fetchPlans({ apiKey, apiUrl: `${testUrl}/api/v1` });
741
904
  printDiff(diffPlans(localPayload?.plans ?? [], testPlans));
742
- p4.log.step(pc4.cyan(`Comparing with LIVE: ${liveUrl}`));
905
+ p4.log.step(pc5.cyan(`Comparing with LIVE: ${liveUrl}`));
743
906
  const livePlans = await fetchPlans({ apiKey, apiUrl: `${liveUrl}/api/v1` });
744
907
  printDiff(diffPlans(localPayload?.plans ?? [], livePlans));
745
908
  } else {
746
909
  s.start("Loading local configuration...");
747
- const owo = await loadOwostackFromConfig(fullPath);
910
+ let owo;
911
+ try {
912
+ owo = await loadOwostackFromConfig(fullPath);
913
+ } catch (e) {
914
+ s.stop(pc5.red("Failed to load configuration"));
915
+ p4.log.error(pc5.red(`Error: ${e.message}`));
916
+ p4.log.info(
917
+ pc5.dim(
918
+ "Make sure 'owostack' is installed in your project: 'npm install owostack'"
919
+ )
920
+ );
921
+ process.exit(1);
922
+ }
748
923
  s.stop("Configuration loaded");
749
924
  const { buildSyncPayload } = await import("owostack").catch(() => ({
750
925
  buildSyncPayload: null
751
926
  }));
752
927
  const localPayload = buildSyncPayload(owo._config.catalog);
753
- s.start(`Fetching remote plans from ${pc4.dim(baseUrl)}...`);
928
+ s.start(`Fetching remote plans from ${pc5.dim(baseUrl)}...`);
754
929
  const remotePlans = await fetchPlans({
755
930
  apiKey,
756
931
  apiUrl: `${baseUrl}/api/v1`
@@ -758,34 +933,44 @@ async function runDiff(options) {
758
933
  s.stop("Remote plans fetched");
759
934
  printDiff(diffPlans(localPayload?.plans ?? [], remotePlans));
760
935
  }
761
- p4.outro(pc4.green("Diff complete \u2728"));
936
+ p4.outro(pc5.green("Diff complete \u2728"));
762
937
  }
763
938
 
764
939
  // src/commands/init.ts
765
940
  import * as p6 from "@clack/prompts";
766
- import pc6 from "picocolors";
767
- import { existsSync as existsSync4 } from "fs";
941
+ import pc7 from "picocolors";
942
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
768
943
  import { writeFile as writeFile3 } from "fs/promises";
944
+ import { join as join3, resolve as resolve3, isAbsolute as isAbsolute3, extname as extname3 } from "path";
769
945
 
770
946
  // src/lib/connect.ts
771
947
  import * as p5 from "@clack/prompts";
772
- import pc5 from "picocolors";
948
+ import pc6 from "picocolors";
773
949
  async function initiateDeviceFlow(options) {
774
950
  const url = `${options.apiUrl}/api/auth/cli/device`;
775
- const response = await fetch(url, {
776
- method: "POST",
777
- headers: { "Content-Type": "application/json" }
778
- });
779
- const data = await response.json();
780
- if (!response.ok || !data?.success) {
781
- const message = data?.error || data?.message || "Failed to initiate device flow";
782
- throw new Error(message);
951
+ try {
952
+ const response = await fetch(url, {
953
+ method: "POST",
954
+ headers: { "Content-Type": "application/json" }
955
+ });
956
+ const data = await response.json();
957
+ if (!response.ok || !data?.success) {
958
+ const message = data?.error || data?.message || "Failed to initiate device flow";
959
+ throw new Error(message);
960
+ }
961
+ return {
962
+ deviceCode: data.deviceCode,
963
+ userCode: data.userCode,
964
+ expiresIn: data.expiresIn || 300
965
+ };
966
+ } catch (error) {
967
+ if (error.name === "TypeError" && error.message.includes("fetch failed")) {
968
+ throw new Error(
969
+ `Could not reach the API at ${options.apiUrl}. Please check your internet connection or ensure the API is running.`
970
+ );
971
+ }
972
+ throw error;
783
973
  }
784
- return {
785
- deviceCode: data.deviceCode,
786
- userCode: data.userCode,
787
- expiresIn: data.expiresIn || 300
788
- };
789
974
  }
790
975
  async function pollForToken(deviceCode, options, s) {
791
976
  const startTime = Date.now();
@@ -793,22 +978,29 @@ async function pollForToken(deviceCode, options, s) {
793
978
  const pollInterval = 3e3;
794
979
  while (Date.now() - startTime < timeoutMs) {
795
980
  const url = `${options.apiUrl}/api/auth/cli/token?deviceCode=${deviceCode}`;
796
- const response = await fetch(url, { method: "GET" });
797
- const data = await response.json();
798
- if (data?.success && data?.apiKey) {
799
- return {
800
- success: true,
801
- apiKey: data.apiKey,
802
- organizationId: data.organizationId
803
- };
804
- }
805
- if (data?.error === "expired") {
806
- throw new Error("Device code expired. Please try again.");
807
- }
808
- if (data?.error === "denied") {
809
- throw new Error("Connection was denied by user.");
981
+ try {
982
+ const response = await fetch(url, { method: "GET" });
983
+ const data = await response.json();
984
+ if (data?.success && data?.apiKey) {
985
+ return {
986
+ success: true,
987
+ apiKey: data.apiKey,
988
+ organizationId: data.organizationId
989
+ };
990
+ }
991
+ if (data?.error === "expired") {
992
+ throw new Error("Device code expired. Please try again.");
993
+ }
994
+ if (data?.error === "denied") {
995
+ throw new Error("Connection was denied by user.");
996
+ }
997
+ } catch (error) {
998
+ if (error.name === "TypeError" && error.message.includes("fetch failed")) {
999
+ } else {
1000
+ throw error;
1001
+ }
810
1002
  }
811
- await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
1003
+ await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
812
1004
  }
813
1005
  throw new Error("Connection timed out. Please try again.");
814
1006
  }
@@ -816,8 +1008,8 @@ async function executeConnectFlow(options) {
816
1008
  try {
817
1009
  const deviceCode = await initiateDeviceFlow(options);
818
1010
  const authUrl = `${options.dashboardUrl}/cli/connect?code=${deviceCode.userCode}`;
819
- p5.log.step(pc5.bold("Connect to your dashboard:"));
820
- p5.log.message(`${pc5.cyan(pc5.underline(authUrl))}
1011
+ p5.log.step(pc6.bold("Connect to your dashboard:"));
1012
+ p5.log.message(`${pc6.cyan(pc6.underline(authUrl))}
821
1013
  `);
822
1014
  const shouldOpen = !options.noBrowser;
823
1015
  if (shouldOpen) {
@@ -831,10 +1023,10 @@ async function executeConnectFlow(options) {
831
1023
  }
832
1024
  const s = p5.spinner();
833
1025
  s.start(
834
- `Waiting for you to approve in the dashboard (Code: ${pc5.bold(pc5.yellow(deviceCode.userCode))})`
1026
+ `Waiting for you to approve in the dashboard (Code: ${pc6.bold(pc6.yellow(deviceCode.userCode))})`
835
1027
  );
836
1028
  const result = await pollForToken(deviceCode.deviceCode, options, s);
837
- s.stop(pc5.green("Authorization granted"));
1029
+ s.stop(pc6.green("Authorization granted"));
838
1030
  if (result.success && result.apiKey) {
839
1031
  await saveGlobalConfig({
840
1032
  apiKey: result.apiKey,
@@ -843,19 +1035,40 @@ async function executeConnectFlow(options) {
843
1035
  return result.apiKey;
844
1036
  }
845
1037
  } catch (e) {
846
- p5.log.error(pc5.red(`\u2717 ${e.message}`));
1038
+ p5.log.error(pc6.red(`\u2717 ${e.message}`));
847
1039
  }
848
1040
  return null;
849
1041
  }
850
1042
 
851
1043
  // src/commands/init.ts
1044
+ function getProjectInfo2() {
1045
+ const cwd = process.cwd();
1046
+ const isTs = existsSync4(join3(cwd, "tsconfig.json"));
1047
+ let isEsm = false;
1048
+ try {
1049
+ const pkg = JSON.parse(readFileSync3(join3(cwd, "package.json"), "utf8"));
1050
+ isEsm = pkg.type === "module";
1051
+ } catch {
1052
+ }
1053
+ return { isTs, isEsm };
1054
+ }
852
1055
  async function runInit(options) {
853
- p6.intro(pc6.bgYellow(pc6.black(" init ")));
854
- const fullPath = resolveConfigPath(options.config);
1056
+ p6.intro(pc7.bgYellow(pc7.black(" init ")));
1057
+ let targetPath = options.config;
1058
+ if (!targetPath) {
1059
+ const existing = resolveConfigPath();
1060
+ if (existing) {
1061
+ targetPath = existing;
1062
+ } else {
1063
+ const { isTs } = getProjectInfo2();
1064
+ targetPath = isTs ? "owo.config.ts" : "owo.config.js";
1065
+ }
1066
+ }
1067
+ const fullPath = isAbsolute3(targetPath) ? targetPath : resolve3(process.cwd(), targetPath);
855
1068
  let apiKey = getApiKey(options.key);
856
1069
  if (!apiKey) {
857
1070
  p6.log.warn(
858
- pc6.yellow("No API key found. Let's connect your account first.")
1071
+ pc7.yellow("No API key found. Let's connect your account first.")
859
1072
  );
860
1073
  apiKey = await executeConnectFlow({
861
1074
  apiUrl: getApiUrl(),
@@ -864,7 +1077,7 @@ async function runInit(options) {
864
1077
  timeout: 300
865
1078
  }) || "";
866
1079
  if (!apiKey) {
867
- p6.log.error(pc6.red("Could not obtain API key. Initialization aborted."));
1080
+ p6.log.error(pc7.red("Could not obtain API key. Initialization aborted."));
868
1081
  process.exit(1);
869
1082
  }
870
1083
  }
@@ -874,7 +1087,7 @@ async function runInit(options) {
874
1087
  initialValue: false
875
1088
  });
876
1089
  if (p6.isCancel(confirm3) || !confirm3) {
877
- p6.outro(pc6.yellow("Initialization cancelled"));
1090
+ p6.outro(pc7.yellow("Initialization cancelled"));
878
1091
  process.exit(0);
879
1092
  }
880
1093
  }
@@ -886,22 +1099,40 @@ async function runInit(options) {
886
1099
  apiKey,
887
1100
  `${getApiUrl()}/api/v1`
888
1101
  );
889
- const configContent = generateConfig(plans, creditSystems);
1102
+ const ext = extname3(fullPath);
1103
+ const { isEsm } = getProjectInfo2();
1104
+ let format = "ts";
1105
+ if (ext === ".ts" || ext === ".mts" || ext === ".cts") {
1106
+ format = "ts";
1107
+ } else if (ext === ".mjs") {
1108
+ format = "esm";
1109
+ } else if (ext === ".cjs") {
1110
+ format = "cjs";
1111
+ } else if (ext === ".js") {
1112
+ format = isEsm ? "esm" : "cjs";
1113
+ }
1114
+ const configContent = generateConfig(
1115
+ plans,
1116
+ creditSystems,
1117
+ void 0,
1118
+ format
1119
+ );
890
1120
  await writeFile3(fullPath, configContent, "utf8");
891
- s.stop(pc6.green("Configuration created"));
1121
+ s.stop(pc7.green("Configuration created"));
892
1122
  p6.note(
893
- `${pc6.dim("File:")} ${fullPath}
894
- ${pc6.dim("Plans:")} ${plans.length} imported
895
- ${pc6.dim("Credit Systems:")} ${creditSystems.length}`,
1123
+ `${pc7.dim("File:")} ${fullPath}
1124
+ ${pc7.dim("Format:")} ${format.toUpperCase()}
1125
+ ${pc7.dim("Plans:")} ${plans.length} imported
1126
+ ${pc7.dim("Credit Systems:")} ${creditSystems.length}`,
896
1127
  "\u2728 Project Initialized"
897
1128
  );
898
1129
  p6.outro(
899
- pc6.cyan(
900
- `Next step: Run ${pc6.bold("owostack sync")} to apply your catalog.`
1130
+ pc7.cyan(
1131
+ `Next step: Run ${pc7.bold("owostack sync")} to apply your catalog.`
901
1132
  )
902
1133
  );
903
1134
  } catch (e) {
904
- s.stop(pc6.red("Initialization failed"));
1135
+ s.stop(pc7.red("Initialization failed"));
905
1136
  p6.log.error(e.message);
906
1137
  process.exit(1);
907
1138
  }
@@ -909,25 +1140,41 @@ ${pc6.dim("Credit Systems:")} ${creditSystems.length}`,
909
1140
 
910
1141
  // src/commands/validate.ts
911
1142
  import * as p7 from "@clack/prompts";
912
- import pc7 from "picocolors";
1143
+ import pc8 from "picocolors";
913
1144
  async function runValidate(options) {
914
- p7.intro(pc7.bgYellow(pc7.black(" validate ")));
1145
+ p7.intro(pc8.bgYellow(pc8.black(" validate ")));
915
1146
  const fullPath = resolveConfigPath(options.config);
1147
+ if (!fullPath) {
1148
+ p7.log.error(pc8.red("No configuration file found."));
1149
+ process.exit(1);
1150
+ }
916
1151
  const s = p7.spinner();
917
- s.start(`Loading ${pc7.cyan(options.config)}`);
918
- const owo = await loadOwostackFromConfig(fullPath);
1152
+ s.start(`Loading ${pc8.cyan(fullPath)}`);
1153
+ let owo;
1154
+ try {
1155
+ owo = await loadOwostackFromConfig(fullPath);
1156
+ } catch (e) {
1157
+ s.stop(pc8.red("Failed to load configuration"));
1158
+ p7.log.error(pc8.red(`Error: ${e.message}`));
1159
+ p7.log.info(
1160
+ pc8.dim(
1161
+ "Make sure 'owostack' is installed in your project: 'npm install owostack'"
1162
+ )
1163
+ );
1164
+ process.exit(1);
1165
+ }
919
1166
  if (!owo || typeof owo.sync !== "function") {
920
- s.stop(pc7.red("Invalid configuration"));
1167
+ s.stop(pc8.red("Invalid configuration"));
921
1168
  p7.log.error("Config file must export an Owostack instance.");
922
1169
  process.exit(1);
923
1170
  }
924
1171
  if (!owo._config?.catalog || owo._config.catalog.length === 0) {
925
- s.stop(pc7.red("No catalog found"));
1172
+ s.stop(pc8.red("No catalog found"));
926
1173
  p7.log.error("Config has no catalog to validate.");
927
1174
  process.exit(1);
928
1175
  }
929
1176
  s.stop(
930
- pc7.green(`Configuration loaded (${owo._config.catalog.length} entries)`)
1177
+ pc8.green(`Configuration loaded (${owo._config.catalog.length} entries)`)
931
1178
  );
932
1179
  const { buildSyncPayload } = await import("owostack").catch(() => ({
933
1180
  buildSyncPayload: null
@@ -938,18 +1185,18 @@ async function runValidate(options) {
938
1185
  }
939
1186
  try {
940
1187
  const payload = buildSyncPayload(owo._config.catalog);
941
- p7.log.step(pc7.bold("Features"));
1188
+ p7.log.step(pc8.bold("Features"));
942
1189
  for (const f of payload.features) {
943
- p7.log.message(`${pc7.green("\u2713")} ${f.slug} ${pc7.dim(`(${f.type})`)}`);
1190
+ p7.log.message(`${pc8.green("\u2713")} ${f.slug} ${pc8.dim(`(${f.type})`)}`);
944
1191
  }
945
- p7.log.step(pc7.bold("Plans"));
1192
+ p7.log.step(pc8.bold("Plans"));
946
1193
  for (const p_obj of payload.plans) {
947
1194
  p7.log.message(
948
- `${pc7.green("\u2713")} ${pc7.bold(p_obj.slug)} ${pc7.dim(`${p_obj.currency} ${p_obj.price} / ${p_obj.interval}`)}`
1195
+ `${pc8.green("\u2713")} ${pc8.bold(p_obj.slug)} ${pc8.dim(`${p_obj.currency} ${p_obj.price} / ${p_obj.interval}`)}`
949
1196
  );
950
1197
  }
951
1198
  if (options.prod) {
952
- p7.log.step(pc7.magenta("Production Mode Check"));
1199
+ p7.log.step(pc8.magenta("Production Mode Check"));
953
1200
  const configSettings = await loadConfigSettings(options.config);
954
1201
  const testUrl = getTestApiUrl(configSettings.environments?.test);
955
1202
  const liveUrl = getApiUrl(configSettings.environments?.live);
@@ -977,7 +1224,7 @@ async function runValidate(options) {
977
1224
  p7.log.error(`LIVE environment check failed: ${e.message}`);
978
1225
  }
979
1226
  }
980
- p7.outro(pc7.green("Validation passed! \u2728"));
1227
+ p7.outro(pc8.green("Validation passed! \u2728"));
981
1228
  } catch (e) {
982
1229
  p7.log.error(`Validation failed: ${e.message}`);
983
1230
  process.exit(1);
@@ -986,16 +1233,16 @@ async function runValidate(options) {
986
1233
 
987
1234
  // src/commands/connect.ts
988
1235
  import * as p8 from "@clack/prompts";
989
- import pc8 from "picocolors";
1236
+ import pc9 from "picocolors";
990
1237
  import { existsSync as existsSync5 } from "fs";
991
1238
  async function runConnect(options) {
992
- p8.intro(pc8.bgYellow(pc8.black(" connect ")));
993
- const configPath = "./owo.config.ts";
1239
+ p8.intro(pc9.bgYellow(pc9.black(" connect ")));
1240
+ const fullPath = resolveConfigPath();
994
1241
  let apiUrl = getApiUrl();
995
1242
  let dashboardUrl = getDashboardUrl();
996
1243
  let noBrowser = options.browser === false;
997
- if (existsSync5(resolveConfigPath(configPath))) {
998
- const configSettings = await loadConfigSettings(configPath);
1244
+ if (fullPath && existsSync5(fullPath)) {
1245
+ const configSettings = await loadConfigSettings(fullPath);
999
1246
  if (configSettings.connect?.dashboardUrl) {
1000
1247
  dashboardUrl = getDashboardUrl(configSettings.connect.dashboardUrl);
1001
1248
  }
@@ -1011,26 +1258,26 @@ async function runConnect(options) {
1011
1258
  });
1012
1259
  if (apiKey) {
1013
1260
  p8.note(
1014
- `${pc8.dim("API Key:")} owo_***
1015
- ${pc8.dim("Config:")} ${GLOBAL_CONFIG_PATH}`,
1261
+ `${pc9.dim("API Key:")} owo_***
1262
+ ${pc9.dim("Config:")} ${GLOBAL_CONFIG_PATH}`,
1016
1263
  "Connected successfully!"
1017
1264
  );
1018
- p8.outro(pc8.green("Authentication complete \u2728"));
1265
+ p8.outro(pc9.green("Authentication complete \u2728"));
1019
1266
  } else {
1020
- p8.log.error(pc8.red("Connection failed. Please try again."));
1267
+ p8.log.error(pc9.red("Connection failed. Please try again."));
1021
1268
  process.exit(1);
1022
1269
  }
1023
1270
  }
1024
1271
 
1025
1272
  // src/lib/brand.ts
1026
- import pc9 from "picocolors";
1273
+ import pc10 from "picocolors";
1027
1274
  var OWO_ASCII = `
1028
- ${pc9.yellow("\u2588\u2588\u2588\u2588\u2588\u2588\u2557")} ${pc9.white("\u2588\u2588\u2557 \u2588\u2588\u2557")} ${pc9.yellow("\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
1029
- ${pc9.yellow("\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557")} ${pc9.white("\u2588\u2588\u2551 \u2588\u2588\u2551")} ${pc9.yellow("\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557")}
1030
- ${pc9.yellow("\u2588\u2588\u2551 \u2588\u2588\u2551")} ${pc9.white("\u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551")} ${pc9.yellow("\u2588\u2588\u2551 \u2588\u2588\u2551")}
1031
- ${pc9.yellow("\u2588\u2588\u2551 \u2588\u2588\u2551")} ${pc9.white("\u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551")} ${pc9.yellow("\u2588\u2588\u2551 \u2588\u2588\u2551")}
1032
- ${pc9.yellow("\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D")} ${pc9.white("\u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D")} ${pc9.yellow("\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D")}
1033
- ${pc9.yellow("\u255A\u2550\u2550\u2550\u2550\u2550\u255D")} ${pc9.white("\u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D")} ${pc9.yellow("\u255A\u2550\u2550\u2550\u2550\u2550\u255D")}
1275
+ ${pc10.yellow("\u2588\u2588\u2588\u2588\u2588\u2588\u2557")} ${pc10.white("\u2588\u2588\u2557 \u2588\u2588\u2557")} ${pc10.yellow("\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
1276
+ ${pc10.yellow("\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557")} ${pc10.white("\u2588\u2588\u2551 \u2588\u2588\u2551")} ${pc10.yellow("\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557")}
1277
+ ${pc10.yellow("\u2588\u2588\u2551 \u2588\u2588\u2551")} ${pc10.white("\u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551")} ${pc10.yellow("\u2588\u2588\u2551 \u2588\u2588\u2551")}
1278
+ ${pc10.yellow("\u2588\u2588\u2551 \u2588\u2588\u2551")} ${pc10.white("\u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551")} ${pc10.yellow("\u2588\u2588\u2551 \u2588\u2588\u2551")}
1279
+ ${pc10.yellow("\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D")} ${pc10.white("\u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D")} ${pc10.yellow("\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D")}
1280
+ ${pc10.yellow("\u255A\u2550\u2550\u2550\u2550\u2550\u255D")} ${pc10.white("\u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D")} ${pc10.yellow("\u255A\u2550\u2550\u2550\u2550\u2550\u255D")}
1034
1281
  `;
1035
1282
  function printBrand() {
1036
1283
  console.log(OWO_ASCII);
@@ -1040,10 +1287,10 @@ function printBrand() {
1040
1287
  var program = new Command();
1041
1288
  printBrand();
1042
1289
  program.name("owostack").description("CLI for Owostack billing infrastructure").version("0.1.0");
1043
- program.command("sync").description("Push catalog to the API").option("--config <path>", "Path to config file", "./owo.config.ts").option("--key <api-key>", "API secret key").option("--prod", "Execute in both test and live environments").option("--dry-run", "Show what would change without applying").action(runSync);
1044
- program.command("pull").description("Pull plans from dashboard into owo.config.ts").option("--config <path>", "Path to config file", "./owo.config.ts").option("--key <api-key>", "API secret key").option("--force", "Overwrite existing config file", false).option("--prod", "Execute in both test and live environments").option("--dry-run", "Show what would change without applying").action(runPull);
1045
- program.command("diff").description("Compare local config to dashboard plans").option("--config <path>", "Path to config file", "./owo.config.ts").option("--key <api-key>", "API secret key").option("--prod", "Execute in both test and live environments").action(runDiff);
1046
- program.command("init").description("Initialize owo.config.ts from dashboard").option("--config <path>", "Path to config file", "./owo.config.ts").option("--key <api-key>", "API secret key").option("--force", "Overwrite existing config file", false).action(runInit);
1047
- program.command("validate").description("Validate local config without syncing").option("--config <path>", "Path to config file", "./owo.config.ts").option("--prod", "Execute in both test and live environments").action(runValidate);
1290
+ program.command("sync").description("Push catalog to the API").option("--config <path>", "Path to config file").option("--key <api-key>", "API secret key").option("--prod", "Execute in both test and live environments").option("--dry-run", "Show what would change without applying").action(runSync);
1291
+ program.command("pull").description("Pull plans from dashboard into owo.config.ts").option("--config <path>", "Path to config file").option("--key <api-key>", "API secret key").option("--force", "Overwrite existing config file", false).option("--prod", "Execute in both test and live environments").option("--dry-run", "Show what would change without applying").action(runPull);
1292
+ program.command("diff").description("Compare local config to dashboard plans").option("--config <path>", "Path to config file").option("--key <api-key>", "API secret key").option("--prod", "Execute in both test and live environments").action(runDiff);
1293
+ program.command("init").description("Initialize owo.config.ts from dashboard").option("--config <path>", "Path to config file").option("--key <api-key>", "API secret key").option("--force", "Overwrite existing config file", false).action(runInit);
1294
+ program.command("validate").description("Validate local config without syncing").option("--config <path>", "Path to config file").option("--prod", "Execute in both test and live environments").action(runValidate);
1048
1295
  program.command("connect").description("Connect CLI to dashboard via browser").option("--no-browser", "Don't open the browser automatically").action(runConnect);
1049
1296
  program.parse();
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "owosk",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "CLI for Owostack - sync catalog, manage billing infrastructure",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "https://github.com/Abdulmumin1/owostack.git",
8
+ "url": "https://github.com/Abdulmumin1/owostack",
9
9
  "directory": "packages/cli"
10
10
  },
11
11
  "type": "module",
@@ -13,6 +13,7 @@
13
13
  "dist"
14
14
  ],
15
15
  "bin": {
16
+ "owosk": "./dist/index.js",
16
17
  "owostack": "./dist/index.js",
17
18
  "owo": "./dist/index.js",
18
19
  "owos": "./dist/index.js",
@@ -23,8 +24,8 @@
23
24
  "commander": "^14.0.3",
24
25
  "jiti": "^2.6.1",
25
26
  "picocolors": "^1.1.1",
26
- "owostack": "0.1.2",
27
- "@owostack/types": "0.1.2"
27
+ "@owostack/types": "0.1.4",
28
+ "owostack": "0.1.4"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@types/node": "^22.19.10",