owosk 0.1.2 → 0.1.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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +399 -165
  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();
@@ -363,14 +450,12 @@ function generateConfig(plans, creditSystems = [], defaultProvider) {
363
450
  const varName = slugToIdentifier(feature.slug, usedNames);
364
451
  featureVars.set(feature.slug, varName);
365
452
  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
- );
453
+ const decl = feature.type === "boolean" ? `boolean(${JSON.stringify(feature.slug)}${nameArg})` : `metered(${JSON.stringify(feature.slug)}${nameArg})`;
454
+ if (isCjs) {
455
+ featureLines.push(`const ${varName} = ${decl};`);
456
+ featureLines.push(`exports.${varName} = ${varName};`);
370
457
  } else {
371
- featureLines.push(
372
- `export const ${varName} = metered(${JSON.stringify(feature.slug)}${nameArg});`
373
- );
458
+ featureLines.push(`export const ${varName} = ${decl};`);
374
459
  }
375
460
  }
376
461
  const creditSystemLines = [];
@@ -389,9 +474,13 @@ function generateConfig(plans, creditSystems = [], defaultProvider) {
389
474
  descArg,
390
475
  `features: [${featureEntries.join(", ")}]`
391
476
  ].filter(Boolean);
392
- creditSystemLines.push(
393
- `export const ${varName} = creditSystem(${JSON.stringify(cs.slug)}, { ${optsParts.join(", ")} });`
394
- );
477
+ const decl = `creditSystem(${JSON.stringify(cs.slug)}, { ${optsParts.join(", ")} })`;
478
+ if (isCjs) {
479
+ creditSystemLines.push(`const ${varName} = ${decl};`);
480
+ creditSystemLines.push(`exports.${varName} = ${varName};`);
481
+ } else {
482
+ creditSystemLines.push(`export const ${varName} = ${decl};`);
483
+ }
395
484
  }
396
485
  const planLines = [];
397
486
  for (const plan of plans) {
@@ -477,44 +566,83 @@ function generateConfig(plans, creditSystems = [], defaultProvider) {
477
566
  const hasCreditSystems = creditSystemLines.length > 0;
478
567
  const providerLine = defaultProvider ? ` provider: ${JSON.stringify(defaultProvider)},
479
568
  ` : "";
569
+ const imports = isCjs ? `const { Owostack, metered, boolean, creditSystem, plan } = require("owostack");` : `import { Owostack, metered, boolean, creditSystem, plan } from "owostack";`;
570
+ const tsCheck = !isTs ? `// @ts-check` : "";
571
+ const jsDoc = !isTs ? `/** @type {import('owostack').Owostack} */` : "";
572
+ const owoDecl = isCjs ? "exports.owo =" : "export const owo =";
573
+ const secretKey = isTs ? "process.env.OWOSTACK_SECRET_KEY!" : "process.env.OWOSTACK_SECRET_KEY";
480
574
  return [
481
- `import { Owostack, metered, boolean, creditSystem, plan } from "owostack";`,
575
+ tsCheck,
576
+ imports,
482
577
  ``,
483
578
  ...featureLines,
484
579
  ...hasCreditSystems ? ["", ...creditSystemLines] : [],
485
580
  ``,
486
- `export const owo = new Owostack({`,
487
- ` secretKey: process.env.OWOSTACK_SECRET_KEY!,`,
581
+ jsDoc,
582
+ `${owoDecl} new Owostack({`,
583
+ ` secretKey: ${secretKey},`,
488
584
  providerLine,
489
585
  ` catalog: [`,
490
586
  ` ${planLines.join(",\n ")}`,
491
587
  ` ],`,
492
588
  `});`,
493
589
  ``
494
- ].join("\n");
590
+ ].filter(Boolean).join("\n");
495
591
  }
496
592
 
497
593
  // src/commands/pull.ts
594
+ function getProjectInfo() {
595
+ const cwd = process.cwd();
596
+ let isEsm = false;
597
+ try {
598
+ const pkg = JSON.parse(readFileSync2(join2(cwd, "package.json"), "utf8"));
599
+ isEsm = pkg.type === "module";
600
+ } catch {
601
+ }
602
+ return { isEsm };
603
+ }
604
+ function determineFormat(fullPath) {
605
+ const ext = extname2(fullPath);
606
+ const { isEsm } = getProjectInfo();
607
+ if (ext === ".ts" || ext === ".mts" || ext === ".cts") return "ts";
608
+ if (ext === ".mjs") return "esm";
609
+ if (ext === ".cjs") return "cjs";
610
+ if (ext === ".js") return isEsm ? "esm" : "cjs";
611
+ return "ts";
612
+ }
498
613
  async function runPull(options) {
499
- p2.intro(pc2.bgYellow(pc2.black(" pull ")));
500
- const fullPath = resolveConfigPath(options.config);
614
+ p2.intro(pc3.bgYellow(pc3.black(" pull ")));
615
+ let fullPath;
616
+ if (options.config) {
617
+ fullPath = isAbsolute2(options.config) ? options.config : resolve2(process.cwd(), options.config);
618
+ } else {
619
+ const resolved = resolveConfigPath();
620
+ if (!resolved) {
621
+ p2.log.error(
622
+ pc3.red("No configuration file found. Run 'owostack init' first.")
623
+ );
624
+ process.exit(1);
625
+ }
626
+ fullPath = resolved;
627
+ }
501
628
  const apiKey = getApiKey(options.key);
502
629
  const configSettings = await loadConfigSettings(options.config);
503
630
  const baseUrl = getApiUrl(configSettings.apiUrl);
504
631
  const filters = configSettings.filters || {};
632
+ const format = determineFormat(fullPath);
505
633
  const s = p2.spinner();
506
634
  if (options.prod) {
507
- p2.log.step(pc2.magenta("Production Mode: Fetching both environments"));
635
+ p2.log.step(pc3.magenta("Production Mode: Fetching both environments"));
508
636
  const testUrl = getTestApiUrl(configSettings.environments?.test);
509
637
  const liveUrl = getApiUrl(configSettings.environments?.live);
510
- s.start(`Fetching from ${pc2.dim("test")}...`);
638
+ s.start(`Fetching from ${pc3.dim("test")}...`);
511
639
  const testPlans = await fetchPlans({
512
640
  apiKey,
513
641
  apiUrl: `${testUrl}/api/v1`,
514
642
  ...filters
515
643
  });
516
644
  s.stop(`Fetched ${testPlans.length} plans from test`);
517
- s.start(`Fetching from ${pc2.dim("live")}...`);
645
+ s.start(`Fetching from ${pc3.dim("live")}...`);
518
646
  const livePlans = await fetchPlans({
519
647
  apiKey,
520
648
  apiUrl: `${liveUrl}/api/v1`,
@@ -531,22 +659,23 @@ async function runPull(options) {
531
659
  const configContent = generateConfig(
532
660
  livePlans,
533
661
  creditSystems,
534
- defaultProvider
662
+ defaultProvider,
663
+ format
535
664
  );
536
665
  if (options.dryRun) {
537
666
  p2.note(configContent, "Generated Config (Dry Run)");
538
- p2.outro(pc2.yellow("Dry run complete. No changes made."));
667
+ p2.outro(pc3.yellow("Dry run complete. No changes made."));
539
668
  return;
540
669
  }
541
670
  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."));
671
+ p2.log.error(pc3.red(`Config file already exists at ${fullPath}`));
672
+ p2.log.info(pc3.dim("Use --force to overwrite."));
544
673
  process.exit(1);
545
674
  }
546
675
  await writeFile2(fullPath, configContent, "utf8");
547
- p2.log.success(pc2.green(`Wrote configuration to ${fullPath}`));
676
+ p2.log.success(pc3.green(`Wrote configuration to ${fullPath}`));
548
677
  } else {
549
- s.start(`Fetching plans from ${pc2.dim(baseUrl)}...`);
678
+ s.start(`Fetching plans from ${pc3.dim(baseUrl)}...`);
550
679
  const plans = await fetchPlans({
551
680
  apiKey,
552
681
  apiUrl: `${baseUrl}/api/v1`,
@@ -560,10 +689,15 @@ async function runPull(options) {
560
689
  plans.map((p9) => p9.provider).filter(Boolean)
561
690
  );
562
691
  const defaultProvider = providers.size === 1 ? Array.from(providers)[0] : void 0;
563
- const configContent = generateConfig(plans, creditSystems, defaultProvider);
692
+ const configContent = generateConfig(
693
+ plans,
694
+ creditSystems,
695
+ defaultProvider,
696
+ format
697
+ );
564
698
  if (options.dryRun) {
565
699
  p2.note(configContent, "Generated Config (Dry Run)");
566
- p2.outro(pc2.yellow("Dry run complete. No changes made."));
700
+ p2.outro(pc3.yellow("Dry run complete. No changes made."));
567
701
  return;
568
702
  }
569
703
  if (existsSync3(fullPath) && !options.force) {
@@ -572,22 +706,22 @@ async function runPull(options) {
572
706
  initialValue: false
573
707
  });
574
708
  if (p2.isCancel(confirm3) || !confirm3) {
575
- p2.outro(pc2.yellow("Operation cancelled"));
709
+ p2.outro(pc3.yellow("Operation cancelled"));
576
710
  process.exit(0);
577
711
  }
578
712
  }
579
713
  await writeFile2(fullPath, configContent, "utf8");
580
- p2.log.success(pc2.green(`Wrote configuration to ${fullPath}`));
714
+ p2.log.success(pc3.green(`Wrote configuration to ${fullPath}`));
581
715
  }
582
- p2.outro(pc2.green("Pull complete! \u2728"));
716
+ p2.outro(pc3.green("Pull complete! \u2728"));
583
717
  }
584
718
 
585
719
  // src/commands/diff.ts
586
720
  import * as p4 from "@clack/prompts";
587
- import pc4 from "picocolors";
721
+ import pc5 from "picocolors";
588
722
 
589
723
  // src/lib/diff.ts
590
- import pc3 from "picocolors";
724
+ import pc4 from "picocolors";
591
725
  import * as p3 from "@clack/prompts";
592
726
  function normalizeFeature(pf) {
593
727
  return {
@@ -647,7 +781,7 @@ function diffPlans(localPlans, remotePlans) {
647
781
  const localVal = JSON.stringify(local[field]);
648
782
  const remoteVal = JSON.stringify(remote[field]);
649
783
  details.push(
650
- `${String(field)}: ${pc3.green(localVal)} \u2192 ${pc3.red(remoteVal)}`
784
+ `${String(field)}: ${pc4.green(localVal)} \u2192 ${pc4.red(remoteVal)}`
651
785
  );
652
786
  }
653
787
  }
@@ -657,20 +791,20 @@ function diffPlans(localPlans, remotePlans) {
657
791
  );
658
792
  for (const fslug of localFeatures.keys()) {
659
793
  if (!remoteFeatures.has(fslug)) {
660
- details.push(`feature ${fslug}: ${pc3.green("[local only]")}`);
794
+ details.push(`feature ${fslug}: ${pc4.green("[local only]")}`);
661
795
  continue;
662
796
  }
663
797
  const lf = localFeatures.get(fslug);
664
798
  const rf = remoteFeatures.get(fslug);
665
799
  if (JSON.stringify(lf) !== JSON.stringify(rf)) {
666
800
  details.push(`feature ${fslug}:`);
667
- details.push(` ${pc3.green(JSON.stringify(lf))}`);
668
- details.push(` ${pc3.red(JSON.stringify(rf))}`);
801
+ details.push(` ${pc4.green(JSON.stringify(lf))}`);
802
+ details.push(` ${pc4.red(JSON.stringify(rf))}`);
669
803
  }
670
804
  }
671
805
  for (const fslug of remoteFeatures.keys()) {
672
806
  if (!localFeatures.has(fslug)) {
673
- details.push(`feature ${fslug}: ${pc3.red("[remote only]")}`);
807
+ details.push(`feature ${fslug}: ${pc4.red("[remote only]")}`);
674
808
  }
675
809
  }
676
810
  if (details.length > 0) {
@@ -681,18 +815,18 @@ function diffPlans(localPlans, remotePlans) {
681
815
  }
682
816
  function printDiff(diff) {
683
817
  if (diff.onlyLocal.length === 0 && diff.onlyRemote.length === 0 && diff.changed.length === 0) {
684
- p3.log.success(pc3.green("No differences found."));
818
+ p3.log.success(pc4.green("No differences found."));
685
819
  return;
686
820
  }
687
821
  if (diff.onlyLocal.length > 0) {
688
822
  p3.note(
689
- diff.onlyLocal.map((slug) => `${pc3.green("+")} ${slug}`).join("\n"),
823
+ diff.onlyLocal.map((slug) => `${pc4.green("+")} ${slug}`).join("\n"),
690
824
  "Only in Local"
691
825
  );
692
826
  }
693
827
  if (diff.onlyRemote.length > 0) {
694
828
  p3.note(
695
- diff.onlyRemote.map((slug) => `${pc3.red("-")} ${slug}`).join("\n"),
829
+ diff.onlyRemote.map((slug) => `${pc4.red("-")} ${slug}`).join("\n"),
696
830
  "Only in Remote"
697
831
  );
698
832
  }
@@ -700,7 +834,7 @@ function printDiff(diff) {
700
834
  let changedText = "";
701
835
  for (const item of diff.changed) {
702
836
  changedText += `
703
- ${pc3.bold(item.slug)}
837
+ ${pc4.bold(item.slug)}
704
838
  `;
705
839
  for (const line of item.details) {
706
840
  changedText += ` ${line}
@@ -710,47 +844,75 @@ ${pc3.bold(item.slug)}
710
844
  p3.note(changedText.trim(), "Changed Plans");
711
845
  }
712
846
  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` : ""
847
+ diff.onlyLocal.length > 0 ? `${pc4.green(diff.onlyLocal.length.toString())} added` : "",
848
+ diff.onlyRemote.length > 0 ? `${pc4.red(diff.onlyRemote.length.toString())} removed` : "",
849
+ diff.changed.length > 0 ? `${pc4.yellow(diff.changed.length.toString())} changed` : ""
716
850
  ].filter(Boolean).join(" ");
717
851
  p3.log.info(summary);
718
852
  }
719
853
 
720
854
  // src/commands/diff.ts
721
855
  async function runDiff(options) {
722
- p4.intro(pc4.bgYellow(pc4.black(" diff ")));
856
+ p4.intro(pc5.bgYellow(pc5.black(" diff ")));
723
857
  const fullPath = resolveConfigPath(options.config);
858
+ if (!fullPath) {
859
+ p4.log.error(pc5.red("No configuration file found."));
860
+ process.exit(1);
861
+ }
724
862
  const apiKey = getApiKey(options.key);
725
863
  const configSettings = await loadConfigSettings(options.config);
726
864
  const baseUrl = getApiUrl(configSettings.apiUrl);
727
865
  const s = p4.spinner();
728
866
  if (options.prod) {
729
- p4.log.step(pc4.magenta("Production Mode: Comparing both environments"));
867
+ p4.log.step(pc5.magenta("Production Mode: Comparing both environments"));
730
868
  const testUrl = getTestApiUrl(configSettings.environments?.test);
731
869
  const liveUrl = getApiUrl(configSettings.environments?.live);
732
870
  s.start("Loading local configuration...");
733
- const owo = await loadOwostackFromConfig(fullPath);
871
+ let owo;
872
+ try {
873
+ owo = await loadOwostackFromConfig(fullPath);
874
+ } catch (e) {
875
+ s.stop(pc5.red("Failed to load configuration"));
876
+ p4.log.error(pc5.red(`Error: ${e.message}`));
877
+ p4.log.info(
878
+ pc5.dim(
879
+ "Make sure 'owostack' is installed in your project: 'npm install owostack'"
880
+ )
881
+ );
882
+ process.exit(1);
883
+ }
734
884
  s.stop("Configuration loaded");
735
885
  const { buildSyncPayload } = await import("owostack").catch(() => ({
736
886
  buildSyncPayload: null
737
887
  }));
738
888
  const localPayload = buildSyncPayload(owo._config.catalog);
739
- p4.log.step(pc4.cyan(`Comparing with TEST: ${testUrl}`));
889
+ p4.log.step(pc5.cyan(`Comparing with TEST: ${testUrl}`));
740
890
  const testPlans = await fetchPlans({ apiKey, apiUrl: `${testUrl}/api/v1` });
741
891
  printDiff(diffPlans(localPayload?.plans ?? [], testPlans));
742
- p4.log.step(pc4.cyan(`Comparing with LIVE: ${liveUrl}`));
892
+ p4.log.step(pc5.cyan(`Comparing with LIVE: ${liveUrl}`));
743
893
  const livePlans = await fetchPlans({ apiKey, apiUrl: `${liveUrl}/api/v1` });
744
894
  printDiff(diffPlans(localPayload?.plans ?? [], livePlans));
745
895
  } else {
746
896
  s.start("Loading local configuration...");
747
- const owo = await loadOwostackFromConfig(fullPath);
897
+ let owo;
898
+ try {
899
+ owo = await loadOwostackFromConfig(fullPath);
900
+ } catch (e) {
901
+ s.stop(pc5.red("Failed to load configuration"));
902
+ p4.log.error(pc5.red(`Error: ${e.message}`));
903
+ p4.log.info(
904
+ pc5.dim(
905
+ "Make sure 'owostack' is installed in your project: 'npm install owostack'"
906
+ )
907
+ );
908
+ process.exit(1);
909
+ }
748
910
  s.stop("Configuration loaded");
749
911
  const { buildSyncPayload } = await import("owostack").catch(() => ({
750
912
  buildSyncPayload: null
751
913
  }));
752
914
  const localPayload = buildSyncPayload(owo._config.catalog);
753
- s.start(`Fetching remote plans from ${pc4.dim(baseUrl)}...`);
915
+ s.start(`Fetching remote plans from ${pc5.dim(baseUrl)}...`);
754
916
  const remotePlans = await fetchPlans({
755
917
  apiKey,
756
918
  apiUrl: `${baseUrl}/api/v1`
@@ -758,34 +920,44 @@ async function runDiff(options) {
758
920
  s.stop("Remote plans fetched");
759
921
  printDiff(diffPlans(localPayload?.plans ?? [], remotePlans));
760
922
  }
761
- p4.outro(pc4.green("Diff complete \u2728"));
923
+ p4.outro(pc5.green("Diff complete \u2728"));
762
924
  }
763
925
 
764
926
  // src/commands/init.ts
765
927
  import * as p6 from "@clack/prompts";
766
- import pc6 from "picocolors";
767
- import { existsSync as existsSync4 } from "fs";
928
+ import pc7 from "picocolors";
929
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
768
930
  import { writeFile as writeFile3 } from "fs/promises";
931
+ import { join as join3, resolve as resolve3, isAbsolute as isAbsolute3, extname as extname3 } from "path";
769
932
 
770
933
  // src/lib/connect.ts
771
934
  import * as p5 from "@clack/prompts";
772
- import pc5 from "picocolors";
935
+ import pc6 from "picocolors";
773
936
  async function initiateDeviceFlow(options) {
774
937
  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);
938
+ try {
939
+ const response = await fetch(url, {
940
+ method: "POST",
941
+ headers: { "Content-Type": "application/json" }
942
+ });
943
+ const data = await response.json();
944
+ if (!response.ok || !data?.success) {
945
+ const message = data?.error || data?.message || "Failed to initiate device flow";
946
+ throw new Error(message);
947
+ }
948
+ return {
949
+ deviceCode: data.deviceCode,
950
+ userCode: data.userCode,
951
+ expiresIn: data.expiresIn || 300
952
+ };
953
+ } catch (error) {
954
+ if (error.name === "TypeError" && error.message.includes("fetch failed")) {
955
+ throw new Error(
956
+ `Could not reach the API at ${options.apiUrl}. Please check your internet connection or ensure the API is running.`
957
+ );
958
+ }
959
+ throw error;
783
960
  }
784
- return {
785
- deviceCode: data.deviceCode,
786
- userCode: data.userCode,
787
- expiresIn: data.expiresIn || 300
788
- };
789
961
  }
790
962
  async function pollForToken(deviceCode, options, s) {
791
963
  const startTime = Date.now();
@@ -793,22 +965,29 @@ async function pollForToken(deviceCode, options, s) {
793
965
  const pollInterval = 3e3;
794
966
  while (Date.now() - startTime < timeoutMs) {
795
967
  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.");
968
+ try {
969
+ const response = await fetch(url, { method: "GET" });
970
+ const data = await response.json();
971
+ if (data?.success && data?.apiKey) {
972
+ return {
973
+ success: true,
974
+ apiKey: data.apiKey,
975
+ organizationId: data.organizationId
976
+ };
977
+ }
978
+ if (data?.error === "expired") {
979
+ throw new Error("Device code expired. Please try again.");
980
+ }
981
+ if (data?.error === "denied") {
982
+ throw new Error("Connection was denied by user.");
983
+ }
984
+ } catch (error) {
985
+ if (error.name === "TypeError" && error.message.includes("fetch failed")) {
986
+ } else {
987
+ throw error;
988
+ }
810
989
  }
811
- await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
990
+ await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
812
991
  }
813
992
  throw new Error("Connection timed out. Please try again.");
814
993
  }
@@ -816,8 +995,8 @@ async function executeConnectFlow(options) {
816
995
  try {
817
996
  const deviceCode = await initiateDeviceFlow(options);
818
997
  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))}
998
+ p5.log.step(pc6.bold("Connect to your dashboard:"));
999
+ p5.log.message(`${pc6.cyan(pc6.underline(authUrl))}
821
1000
  `);
822
1001
  const shouldOpen = !options.noBrowser;
823
1002
  if (shouldOpen) {
@@ -831,10 +1010,10 @@ async function executeConnectFlow(options) {
831
1010
  }
832
1011
  const s = p5.spinner();
833
1012
  s.start(
834
- `Waiting for you to approve in the dashboard (Code: ${pc5.bold(pc5.yellow(deviceCode.userCode))})`
1013
+ `Waiting for you to approve in the dashboard (Code: ${pc6.bold(pc6.yellow(deviceCode.userCode))})`
835
1014
  );
836
1015
  const result = await pollForToken(deviceCode.deviceCode, options, s);
837
- s.stop(pc5.green("Authorization granted"));
1016
+ s.stop(pc6.green("Authorization granted"));
838
1017
  if (result.success && result.apiKey) {
839
1018
  await saveGlobalConfig({
840
1019
  apiKey: result.apiKey,
@@ -843,19 +1022,40 @@ async function executeConnectFlow(options) {
843
1022
  return result.apiKey;
844
1023
  }
845
1024
  } catch (e) {
846
- p5.log.error(pc5.red(`\u2717 ${e.message}`));
1025
+ p5.log.error(pc6.red(`\u2717 ${e.message}`));
847
1026
  }
848
1027
  return null;
849
1028
  }
850
1029
 
851
1030
  // src/commands/init.ts
1031
+ function getProjectInfo2() {
1032
+ const cwd = process.cwd();
1033
+ const isTs = existsSync4(join3(cwd, "tsconfig.json"));
1034
+ let isEsm = false;
1035
+ try {
1036
+ const pkg = JSON.parse(readFileSync3(join3(cwd, "package.json"), "utf8"));
1037
+ isEsm = pkg.type === "module";
1038
+ } catch {
1039
+ }
1040
+ return { isTs, isEsm };
1041
+ }
852
1042
  async function runInit(options) {
853
- p6.intro(pc6.bgYellow(pc6.black(" init ")));
854
- const fullPath = resolveConfigPath(options.config);
1043
+ p6.intro(pc7.bgYellow(pc7.black(" init ")));
1044
+ let targetPath = options.config;
1045
+ if (!targetPath) {
1046
+ const existing = resolveConfigPath();
1047
+ if (existing) {
1048
+ targetPath = existing;
1049
+ } else {
1050
+ const { isTs } = getProjectInfo2();
1051
+ targetPath = isTs ? "owo.config.ts" : "owo.config.js";
1052
+ }
1053
+ }
1054
+ const fullPath = isAbsolute3(targetPath) ? targetPath : resolve3(process.cwd(), targetPath);
855
1055
  let apiKey = getApiKey(options.key);
856
1056
  if (!apiKey) {
857
1057
  p6.log.warn(
858
- pc6.yellow("No API key found. Let's connect your account first.")
1058
+ pc7.yellow("No API key found. Let's connect your account first.")
859
1059
  );
860
1060
  apiKey = await executeConnectFlow({
861
1061
  apiUrl: getApiUrl(),
@@ -864,7 +1064,7 @@ async function runInit(options) {
864
1064
  timeout: 300
865
1065
  }) || "";
866
1066
  if (!apiKey) {
867
- p6.log.error(pc6.red("Could not obtain API key. Initialization aborted."));
1067
+ p6.log.error(pc7.red("Could not obtain API key. Initialization aborted."));
868
1068
  process.exit(1);
869
1069
  }
870
1070
  }
@@ -874,7 +1074,7 @@ async function runInit(options) {
874
1074
  initialValue: false
875
1075
  });
876
1076
  if (p6.isCancel(confirm3) || !confirm3) {
877
- p6.outro(pc6.yellow("Initialization cancelled"));
1077
+ p6.outro(pc7.yellow("Initialization cancelled"));
878
1078
  process.exit(0);
879
1079
  }
880
1080
  }
@@ -886,22 +1086,40 @@ async function runInit(options) {
886
1086
  apiKey,
887
1087
  `${getApiUrl()}/api/v1`
888
1088
  );
889
- const configContent = generateConfig(plans, creditSystems);
1089
+ const ext = extname3(fullPath);
1090
+ const { isEsm } = getProjectInfo2();
1091
+ let format = "ts";
1092
+ if (ext === ".ts" || ext === ".mts" || ext === ".cts") {
1093
+ format = "ts";
1094
+ } else if (ext === ".mjs") {
1095
+ format = "esm";
1096
+ } else if (ext === ".cjs") {
1097
+ format = "cjs";
1098
+ } else if (ext === ".js") {
1099
+ format = isEsm ? "esm" : "cjs";
1100
+ }
1101
+ const configContent = generateConfig(
1102
+ plans,
1103
+ creditSystems,
1104
+ void 0,
1105
+ format
1106
+ );
890
1107
  await writeFile3(fullPath, configContent, "utf8");
891
- s.stop(pc6.green("Configuration created"));
1108
+ s.stop(pc7.green("Configuration created"));
892
1109
  p6.note(
893
- `${pc6.dim("File:")} ${fullPath}
894
- ${pc6.dim("Plans:")} ${plans.length} imported
895
- ${pc6.dim("Credit Systems:")} ${creditSystems.length}`,
1110
+ `${pc7.dim("File:")} ${fullPath}
1111
+ ${pc7.dim("Format:")} ${format.toUpperCase()}
1112
+ ${pc7.dim("Plans:")} ${plans.length} imported
1113
+ ${pc7.dim("Credit Systems:")} ${creditSystems.length}`,
896
1114
  "\u2728 Project Initialized"
897
1115
  );
898
1116
  p6.outro(
899
- pc6.cyan(
900
- `Next step: Run ${pc6.bold("owostack sync")} to apply your catalog.`
1117
+ pc7.cyan(
1118
+ `Next step: Run ${pc7.bold("owostack sync")} to apply your catalog.`
901
1119
  )
902
1120
  );
903
1121
  } catch (e) {
904
- s.stop(pc6.red("Initialization failed"));
1122
+ s.stop(pc7.red("Initialization failed"));
905
1123
  p6.log.error(e.message);
906
1124
  process.exit(1);
907
1125
  }
@@ -909,25 +1127,41 @@ ${pc6.dim("Credit Systems:")} ${creditSystems.length}`,
909
1127
 
910
1128
  // src/commands/validate.ts
911
1129
  import * as p7 from "@clack/prompts";
912
- import pc7 from "picocolors";
1130
+ import pc8 from "picocolors";
913
1131
  async function runValidate(options) {
914
- p7.intro(pc7.bgYellow(pc7.black(" validate ")));
1132
+ p7.intro(pc8.bgYellow(pc8.black(" validate ")));
915
1133
  const fullPath = resolveConfigPath(options.config);
1134
+ if (!fullPath) {
1135
+ p7.log.error(pc8.red("No configuration file found."));
1136
+ process.exit(1);
1137
+ }
916
1138
  const s = p7.spinner();
917
- s.start(`Loading ${pc7.cyan(options.config)}`);
918
- const owo = await loadOwostackFromConfig(fullPath);
1139
+ s.start(`Loading ${pc8.cyan(fullPath)}`);
1140
+ let owo;
1141
+ try {
1142
+ owo = await loadOwostackFromConfig(fullPath);
1143
+ } catch (e) {
1144
+ s.stop(pc8.red("Failed to load configuration"));
1145
+ p7.log.error(pc8.red(`Error: ${e.message}`));
1146
+ p7.log.info(
1147
+ pc8.dim(
1148
+ "Make sure 'owostack' is installed in your project: 'npm install owostack'"
1149
+ )
1150
+ );
1151
+ process.exit(1);
1152
+ }
919
1153
  if (!owo || typeof owo.sync !== "function") {
920
- s.stop(pc7.red("Invalid configuration"));
1154
+ s.stop(pc8.red("Invalid configuration"));
921
1155
  p7.log.error("Config file must export an Owostack instance.");
922
1156
  process.exit(1);
923
1157
  }
924
1158
  if (!owo._config?.catalog || owo._config.catalog.length === 0) {
925
- s.stop(pc7.red("No catalog found"));
1159
+ s.stop(pc8.red("No catalog found"));
926
1160
  p7.log.error("Config has no catalog to validate.");
927
1161
  process.exit(1);
928
1162
  }
929
1163
  s.stop(
930
- pc7.green(`Configuration loaded (${owo._config.catalog.length} entries)`)
1164
+ pc8.green(`Configuration loaded (${owo._config.catalog.length} entries)`)
931
1165
  );
932
1166
  const { buildSyncPayload } = await import("owostack").catch(() => ({
933
1167
  buildSyncPayload: null
@@ -938,18 +1172,18 @@ async function runValidate(options) {
938
1172
  }
939
1173
  try {
940
1174
  const payload = buildSyncPayload(owo._config.catalog);
941
- p7.log.step(pc7.bold("Features"));
1175
+ p7.log.step(pc8.bold("Features"));
942
1176
  for (const f of payload.features) {
943
- p7.log.message(`${pc7.green("\u2713")} ${f.slug} ${pc7.dim(`(${f.type})`)}`);
1177
+ p7.log.message(`${pc8.green("\u2713")} ${f.slug} ${pc8.dim(`(${f.type})`)}`);
944
1178
  }
945
- p7.log.step(pc7.bold("Plans"));
1179
+ p7.log.step(pc8.bold("Plans"));
946
1180
  for (const p_obj of payload.plans) {
947
1181
  p7.log.message(
948
- `${pc7.green("\u2713")} ${pc7.bold(p_obj.slug)} ${pc7.dim(`${p_obj.currency} ${p_obj.price} / ${p_obj.interval}`)}`
1182
+ `${pc8.green("\u2713")} ${pc8.bold(p_obj.slug)} ${pc8.dim(`${p_obj.currency} ${p_obj.price} / ${p_obj.interval}`)}`
949
1183
  );
950
1184
  }
951
1185
  if (options.prod) {
952
- p7.log.step(pc7.magenta("Production Mode Check"));
1186
+ p7.log.step(pc8.magenta("Production Mode Check"));
953
1187
  const configSettings = await loadConfigSettings(options.config);
954
1188
  const testUrl = getTestApiUrl(configSettings.environments?.test);
955
1189
  const liveUrl = getApiUrl(configSettings.environments?.live);
@@ -977,7 +1211,7 @@ async function runValidate(options) {
977
1211
  p7.log.error(`LIVE environment check failed: ${e.message}`);
978
1212
  }
979
1213
  }
980
- p7.outro(pc7.green("Validation passed! \u2728"));
1214
+ p7.outro(pc8.green("Validation passed! \u2728"));
981
1215
  } catch (e) {
982
1216
  p7.log.error(`Validation failed: ${e.message}`);
983
1217
  process.exit(1);
@@ -986,16 +1220,16 @@ async function runValidate(options) {
986
1220
 
987
1221
  // src/commands/connect.ts
988
1222
  import * as p8 from "@clack/prompts";
989
- import pc8 from "picocolors";
1223
+ import pc9 from "picocolors";
990
1224
  import { existsSync as existsSync5 } from "fs";
991
1225
  async function runConnect(options) {
992
- p8.intro(pc8.bgYellow(pc8.black(" connect ")));
993
- const configPath = "./owo.config.ts";
1226
+ p8.intro(pc9.bgYellow(pc9.black(" connect ")));
1227
+ const fullPath = resolveConfigPath();
994
1228
  let apiUrl = getApiUrl();
995
1229
  let dashboardUrl = getDashboardUrl();
996
1230
  let noBrowser = options.browser === false;
997
- if (existsSync5(resolveConfigPath(configPath))) {
998
- const configSettings = await loadConfigSettings(configPath);
1231
+ if (fullPath && existsSync5(fullPath)) {
1232
+ const configSettings = await loadConfigSettings(fullPath);
999
1233
  if (configSettings.connect?.dashboardUrl) {
1000
1234
  dashboardUrl = getDashboardUrl(configSettings.connect.dashboardUrl);
1001
1235
  }
@@ -1011,26 +1245,26 @@ async function runConnect(options) {
1011
1245
  });
1012
1246
  if (apiKey) {
1013
1247
  p8.note(
1014
- `${pc8.dim("API Key:")} owo_***
1015
- ${pc8.dim("Config:")} ${GLOBAL_CONFIG_PATH}`,
1248
+ `${pc9.dim("API Key:")} owo_***
1249
+ ${pc9.dim("Config:")} ${GLOBAL_CONFIG_PATH}`,
1016
1250
  "Connected successfully!"
1017
1251
  );
1018
- p8.outro(pc8.green("Authentication complete \u2728"));
1252
+ p8.outro(pc9.green("Authentication complete \u2728"));
1019
1253
  } else {
1020
- p8.log.error(pc8.red("Connection failed. Please try again."));
1254
+ p8.log.error(pc9.red("Connection failed. Please try again."));
1021
1255
  process.exit(1);
1022
1256
  }
1023
1257
  }
1024
1258
 
1025
1259
  // src/lib/brand.ts
1026
- import pc9 from "picocolors";
1260
+ import pc10 from "picocolors";
1027
1261
  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")}
1262
+ ${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")}
1263
+ ${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")}
1264
+ ${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")}
1265
+ ${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")}
1266
+ ${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")}
1267
+ ${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
1268
  `;
1035
1269
  function printBrand() {
1036
1270
  console.log(OWO_ASCII);
@@ -1040,10 +1274,10 @@ function printBrand() {
1040
1274
  var program = new Command();
1041
1275
  printBrand();
1042
1276
  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);
1277
+ 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);
1278
+ 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);
1279
+ 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);
1280
+ 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);
1281
+ 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
1282
  program.command("connect").description("Connect CLI to dashboard via browser").option("--no-browser", "Don't open the browser automatically").action(runConnect);
1049
1283
  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.3",
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.2",
28
+ "owostack": "0.1.2"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@types/node": "^22.19.10",