commet 1.9.1 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1324 -429
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -24,13 +24,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/index.ts
27
- var import_chalk14 = __toESM(require("chalk"));
28
- var import_commander12 = require("commander");
27
+ var import_chalk15 = __toESM(require("chalk"));
28
+ var import_commander13 = require("commander");
29
29
 
30
30
  // package.json
31
31
  var package_default = {
32
32
  name: "commet",
33
- version: "1.9.1",
33
+ version: "1.11.0",
34
34
  description: "Commet CLI - Manage your billing platform from the command line",
35
35
  bin: {
36
36
  commet: "./bin/commet"
@@ -61,6 +61,7 @@ var package_default = {
61
61
  ably: "^2.21.0",
62
62
  chalk: "5.6.2",
63
63
  commander: "14.0.3",
64
+ jiti: "^2.7.0",
64
65
  "jsonc-parser": "3.3.1",
65
66
  open: "11.0.0",
66
67
  ora: "9.4.0",
@@ -86,16 +87,8 @@ var package_default = {
86
87
  }
87
88
  };
88
89
 
89
- // src/commands/create.ts
90
- var import_node_child_process = require("child_process");
91
- var fs2 = __toESM(require("fs"));
92
- var os2 = __toESM(require("os"));
93
- var path2 = __toESM(require("path"));
94
- var import_prompts = require("@inquirer/prompts");
95
- var import_chalk3 = __toESM(require("chalk"));
90
+ // src/commands/agent-info.ts
96
91
  var import_commander = require("commander");
97
- var import_ora2 = __toESM(require("ora"));
98
- var import_tar = require("tar");
99
92
 
100
93
  // src/utils/config.ts
101
94
  var fs = __toESM(require("fs"));
@@ -164,6 +157,211 @@ function clearProjectConfig() {
164
157
  }
165
158
  }
166
159
 
160
+ // src/utils/config-loader.ts
161
+ var fs2 = __toESM(require("fs"));
162
+ var path2 = __toESM(require("path"));
163
+ var import_jiti = require("jiti");
164
+ var CONFIG_NAMES = [
165
+ "commet.config.ts",
166
+ "commet.config.js",
167
+ "commet.config.mjs"
168
+ ];
169
+ function findConfigFile(cwd) {
170
+ for (const name of CONFIG_NAMES) {
171
+ const fullPath = path2.resolve(cwd, name);
172
+ if (fs2.existsSync(fullPath)) {
173
+ return fullPath;
174
+ }
175
+ }
176
+ return null;
177
+ }
178
+ async function loadBillingConfig(cwd) {
179
+ const configPath = findConfigFile(cwd);
180
+ if (!configPath) {
181
+ throw new Error(
182
+ `No commet.config.ts found in ${cwd}. Create one with defineConfig() or run 'commet pull' to generate it.`
183
+ );
184
+ }
185
+ const jiti = (0, import_jiti.createJiti)(configPath, { interopDefault: true });
186
+ const mod = await jiti.import(configPath);
187
+ if (!mod || typeof mod !== "object") {
188
+ throw new Error(`${configPath}: failed to load config module`);
189
+ }
190
+ const moduleRecord = mod;
191
+ if (!moduleRecord.default) {
192
+ throw new Error(
193
+ `${configPath}: must use \`export default defineConfig({...})\``
194
+ );
195
+ }
196
+ const config = moduleRecord.default;
197
+ validateConfig(config, configPath);
198
+ return { config, configPath };
199
+ }
200
+ var VALID_FEATURE_TYPES = /* @__PURE__ */ new Set(["boolean", "usage", "seats"]);
201
+ var VALID_INTERVALS = /* @__PURE__ */ new Set([
202
+ "weekly",
203
+ "monthly",
204
+ "quarterly",
205
+ "yearly",
206
+ "one_time"
207
+ ]);
208
+ function validateConfig(config, configPath) {
209
+ if (!config || typeof config !== "object") {
210
+ throw new Error(`${configPath}: config must be an object`);
211
+ }
212
+ if (!config.features || typeof config.features !== "object") {
213
+ throw new Error(`${configPath}: config.features must be an object`);
214
+ }
215
+ if (!config.plans || typeof config.plans !== "object") {
216
+ throw new Error(`${configPath}: config.plans must be an object`);
217
+ }
218
+ for (const [code, feature] of Object.entries(config.features)) {
219
+ if (!feature.name || typeof feature.name !== "string") {
220
+ throw new Error(`Feature "${code}": name is required`);
221
+ }
222
+ if (!VALID_FEATURE_TYPES.has(feature.type)) {
223
+ throw new Error(
224
+ `Feature "${code}": type must be one of: boolean, usage, seats`
225
+ );
226
+ }
227
+ }
228
+ for (const [code, plan] of Object.entries(config.plans)) {
229
+ if (!plan.name || typeof plan.name !== "string") {
230
+ throw new Error(`Plan "${code}": name is required`);
231
+ }
232
+ if (!Array.isArray(plan.prices)) {
233
+ throw new Error(`Plan "${code}": prices must be an array`);
234
+ }
235
+ for (const price of plan.prices) {
236
+ if (!VALID_INTERVALS.has(price.interval)) {
237
+ throw new Error(
238
+ `Plan "${code}": price interval "${price.interval}" is not valid`
239
+ );
240
+ }
241
+ if (typeof price.amount !== "number") {
242
+ throw new Error(`Plan "${code}": price amount must be a number`);
243
+ }
244
+ }
245
+ if (plan.prices.length > 0) {
246
+ if (!plan.defaultInterval) {
247
+ throw new Error(
248
+ `Plan "${code}": defaultInterval is required when prices are defined`
249
+ );
250
+ }
251
+ const priceIntervals = new Set(plan.prices.map((p) => p.interval));
252
+ if (!priceIntervals.has(plan.defaultInterval)) {
253
+ throw new Error(
254
+ `Plan "${code}": defaultInterval "${plan.defaultInterval}" does not match any price interval (${[...priceIntervals].join(", ")})`
255
+ );
256
+ }
257
+ }
258
+ if (plan.features) {
259
+ for (const featureCode of Object.keys(plan.features)) {
260
+ if (!config.features[featureCode]) {
261
+ throw new Error(
262
+ `Plan "${code}": references feature "${featureCode}" which is not defined in config.features`
263
+ );
264
+ }
265
+ }
266
+ }
267
+ }
268
+ }
269
+
270
+ // src/commands/agent-info.ts
271
+ var agentInfoCommand = new import_commander.Command("agent-info").description(
272
+ "Print project status and CLI capabilities as JSON. Designed for AI agents and CI pipelines \u2014 run this first to discover what commands are available and how to call them non-interactively."
273
+ ).action(() => {
274
+ const authenticated = authExists();
275
+ const projectConfig = projectConfigExists() ? loadProjectConfig() : null;
276
+ const configPath = findConfigFile(process.cwd());
277
+ const setup = [];
278
+ if (!authenticated) {
279
+ setup.push(
280
+ "Not authenticated. A human must run 'commet login' in a terminal with browser access \u2014 this is the only interactive step."
281
+ );
282
+ }
283
+ if (authenticated && !projectConfig) {
284
+ setup.push(
285
+ "No project linked. Run 'commet link --org <slug>' or 'commet orgs --json' to find available organizations."
286
+ );
287
+ }
288
+ const output = {
289
+ version: package_default.version,
290
+ authenticated,
291
+ ...setup.length > 0 ? { setup } : {},
292
+ project: projectConfig ? {
293
+ linked: true,
294
+ orgId: projectConfig.orgId,
295
+ orgName: projectConfig.orgName,
296
+ mode: projectConfig.mode
297
+ } : { linked: false },
298
+ config: {
299
+ exists: configPath !== null,
300
+ path: configPath?.split("/").pop() ?? null
301
+ },
302
+ commands: {
303
+ pull: {
304
+ description: "Pull remote config and generate commet.config.ts",
305
+ usage: "commet pull --json --yes",
306
+ preview: "commet pull --json --dry-run"
307
+ },
308
+ push: {
309
+ description: "Push commet.config.ts to remote",
310
+ usage: "commet push --json --yes",
311
+ preview: "commet push --json --dry-run"
312
+ },
313
+ list: {
314
+ description: "List resources from remote",
315
+ usage: "commet list <features|plans|seats> --json"
316
+ },
317
+ orgs: {
318
+ description: "List available organizations",
319
+ usage: "commet orgs --json"
320
+ },
321
+ link: {
322
+ description: "Link project to an organization",
323
+ usage: "commet link --org <slug-or-id>"
324
+ },
325
+ switch: {
326
+ description: "Switch to a different organization",
327
+ usage: "commet switch --org <slug-or-id>"
328
+ },
329
+ listen: {
330
+ description: "Forward webhook events to local server. Long-running streaming process \u2014 run in background.",
331
+ usage: "commet listen <url> [--events <types>]"
332
+ },
333
+ create: {
334
+ description: "Scaffold a new Commet app from template",
335
+ usage: "commet create [name] -t <template> --org <slug> -y"
336
+ },
337
+ unlink: {
338
+ description: "Unlink project from organization",
339
+ usage: "commet unlink"
340
+ },
341
+ login: {
342
+ description: "Authenticate via browser. Requires a human \u2014 opens a device-code flow that must be confirmed in a browser.",
343
+ usage: "commet login"
344
+ },
345
+ logout: {
346
+ description: "Log out of Commet",
347
+ usage: "commet logout"
348
+ }
349
+ }
350
+ };
351
+ console.log(JSON.stringify(output, null, 2));
352
+ });
353
+
354
+ // src/commands/create.ts
355
+ var import_node_child_process = require("child_process");
356
+ var fs3 = __toESM(require("fs"));
357
+ var os2 = __toESM(require("os"));
358
+ var path3 = __toESM(require("path"));
359
+ var import_prompts = require("@inquirer/prompts");
360
+ var import_chalk3 = __toESM(require("chalk"));
361
+ var import_commander2 = require("commander");
362
+ var import_ora2 = __toESM(require("ora"));
363
+ var import_tar = require("tar");
364
+
167
365
  // src/utils/api.ts
168
366
  var BASE_URL = "https://commet.co";
169
367
  async function apiRequest(endpoint, options = {}) {
@@ -218,7 +416,7 @@ var promptTheme = {
218
416
 
219
417
  // src/utils/login-flow.ts
220
418
  function sleep(ms) {
221
- return new Promise((resolve3) => setTimeout(resolve3, ms));
419
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
222
420
  }
223
421
  async function performLogin() {
224
422
  const spinner = (0, import_ora.default)("Initiating login flow...").start();
@@ -362,11 +560,11 @@ async function downloadTemplate(templateDir, dest, ref) {
362
560
  if (!response.ok) {
363
561
  throw new Error(`Failed to download (HTTP ${response.status})`);
364
562
  }
365
- const tempFile = path2.join(os2.tmpdir(), `commet-${Date.now()}.tar.gz`);
563
+ const tempFile = path3.join(os2.tmpdir(), `commet-${Date.now()}.tar.gz`);
366
564
  try {
367
565
  const buffer = Buffer.from(await response.arrayBuffer());
368
- fs2.writeFileSync(tempFile, buffer);
369
- fs2.mkdirSync(dest, { recursive: true });
566
+ fs3.writeFileSync(tempFile, buffer);
567
+ fs3.mkdirSync(dest, { recursive: true });
370
568
  await (0, import_tar.extract)({
371
569
  file: tempFile,
372
570
  cwd: dest,
@@ -377,22 +575,22 @@ async function downloadTemplate(templateDir, dest, ref) {
377
575
  }
378
576
  });
379
577
  } finally {
380
- if (fs2.existsSync(tempFile)) {
381
- fs2.unlinkSync(tempFile);
578
+ if (fs3.existsSync(tempFile)) {
579
+ fs3.unlinkSync(tempFile);
382
580
  }
383
581
  }
384
- const files = fs2.readdirSync(dest);
582
+ const files = fs3.readdirSync(dest);
385
583
  if (files.length === 0) {
386
584
  throw new Error(`Template "${templateDir}" not found in repository`);
387
585
  }
388
586
  }
389
587
  function updatePackageJson(dest, projectName) {
390
- const pkgPath = path2.join(dest, "package.json");
391
- const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
588
+ const pkgPath = path3.join(dest, "package.json");
589
+ const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
392
590
  pkg.name = projectName;
393
591
  pkg.version = "0.0.1";
394
592
  pkg.private = true;
395
- fs2.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
593
+ fs3.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
396
594
  `);
397
595
  }
398
596
  async function fetchLatestVersion(packageName) {
@@ -408,8 +606,8 @@ async function fetchLatestVersion(packageName) {
408
606
  return data.version;
409
607
  }
410
608
  async function resolveWorkspaceDeps(dest) {
411
- const pkgPath = path2.join(dest, "package.json");
412
- const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
609
+ const pkgPath = path3.join(dest, "package.json");
610
+ const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
413
611
  const depSections = [
414
612
  "dependencies",
415
613
  "devDependencies",
@@ -446,29 +644,29 @@ async function resolveWorkspaceDeps(dest) {
446
644
  pkg[section][name] = version;
447
645
  }
448
646
  }
449
- fs2.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
647
+ fs3.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
450
648
  `);
451
649
  return workspaceDeps.size;
452
650
  }
453
651
  function copyEnvExample(dest) {
454
- const examplePath = path2.join(dest, ".env.example");
455
- const envPath = path2.join(dest, ".env");
456
- if (fs2.existsSync(examplePath)) {
457
- fs2.copyFileSync(examplePath, envPath);
652
+ const examplePath = path3.join(dest, ".env.example");
653
+ const envPath = path3.join(dest, ".env");
654
+ if (fs3.existsSync(examplePath)) {
655
+ fs3.copyFileSync(examplePath, envPath);
458
656
  }
459
657
  }
460
658
  function writeApiKeyToEnv(dest, apiKey) {
461
- const envPath = path2.join(dest, ".env");
462
- if (!fs2.existsSync(envPath)) return;
463
- let content = fs2.readFileSync(envPath, "utf-8");
659
+ const envPath = path3.join(dest, ".env");
660
+ if (!fs3.existsSync(envPath)) return;
661
+ let content = fs3.readFileSync(envPath, "utf-8");
464
662
  content = content.replace(/COMMET_API_KEY=.*/, `COMMET_API_KEY=${apiKey}`);
465
- fs2.writeFileSync(envPath, content);
663
+ fs3.writeFileSync(envPath, content);
466
664
  }
467
665
  function linkProject(dest, orgId, orgName) {
468
- const commetDir = path2.join(dest, ".commet");
469
- fs2.mkdirSync(commetDir, { recursive: true });
470
- fs2.writeFileSync(
471
- path2.join(commetDir, "config.json"),
666
+ const commetDir = path3.join(dest, ".commet");
667
+ fs3.mkdirSync(commetDir, { recursive: true });
668
+ fs3.writeFileSync(
669
+ path3.join(commetDir, "config.json"),
472
670
  JSON.stringify({ orgId, orgName, mode: "sandbox" }, null, 2),
473
671
  "utf8"
474
672
  );
@@ -492,7 +690,7 @@ async function resolveSkills(opts) {
492
690
  }
493
691
  async function installSkills(projectRoot) {
494
692
  const npx = process.platform === "win32" ? "npx.cmd" : "npx";
495
- return new Promise((resolve3) => {
693
+ return new Promise((resolve4) => {
496
694
  const child = (0, import_node_child_process.spawn)(
497
695
  npx,
498
696
  ["-y", "--loglevel=error", "skills", "add", "commet-labs/commet-skills"],
@@ -503,11 +701,11 @@ async function installSkills(projectRoot) {
503
701
  console.log(import_chalk3.default.dim(" You can install them manually by running:"));
504
702
  console.log(import_chalk3.default.dim(" npx skills add commet-labs/commet-skills"));
505
703
  }
506
- resolve3();
704
+ resolve4();
507
705
  });
508
706
  });
509
707
  }
510
- var createCommand = new import_commander.Command("create").description("Create a new Commet app from a template").argument("[name]", "Project name").option(
708
+ var createCommand = new import_commander2.Command("create").description("Create a new Commet app from a template").argument("[name]", "Project name").option(
511
709
  "-t, --template <template>",
512
710
  "Template to use (fixed, seats, metered, credits, balance-ai, balance-fixed)"
513
711
  ).option("--org <slug>", "Organization slug or ID (skips selection)").option("--skills", "Install agent skills").option("--no-skills", "Skip agent skills installation").option("-y, --yes", "Accept defaults for optional prompts").option("--ref <ref>", "Git ref to fetch templates from", "main").option("--list", "List available templates").action(async (argName, opts) => {
@@ -540,8 +738,8 @@ var createCommand = new import_commander.Command("create").description("Create a
540
738
  return;
541
739
  }
542
740
  }
543
- const dest = path2.resolve(projectName);
544
- if (fs2.existsSync(dest)) {
741
+ const dest = path3.resolve(projectName);
742
+ if (fs3.existsSync(dest)) {
545
743
  console.log(
546
744
  import_chalk3.default.red(`\u2717 Directory "${projectName}" already exists`)
547
745
  );
@@ -661,8 +859,8 @@ var createCommand = new import_commander.Command("create").description("Create a
661
859
  if (error instanceof Error) {
662
860
  console.error(import_chalk3.default.red(error.message));
663
861
  }
664
- if (fs2.existsSync(dest)) {
665
- fs2.rmSync(dest, { recursive: true, force: true });
862
+ if (fs3.existsSync(dest)) {
863
+ fs3.rmSync(dest, { recursive: true, force: true });
666
864
  }
667
865
  return;
668
866
  }
@@ -681,8 +879,8 @@ var createCommand = new import_commander.Command("create").description("Create a
681
879
  if (error instanceof Error) {
682
880
  console.error(import_chalk3.default.red(error.message));
683
881
  }
684
- if (fs2.existsSync(dest)) {
685
- fs2.rmSync(dest, { recursive: true, force: true });
882
+ if (fs3.existsSync(dest)) {
883
+ fs3.rmSync(dest, { recursive: true, force: true });
686
884
  }
687
885
  return;
688
886
  }
@@ -733,54 +931,22 @@ var createCommand = new import_commander.Command("create").description("Create a
733
931
  console.log();
734
932
  });
735
933
 
736
- // src/commands/info.ts
737
- var import_chalk4 = __toESM(require("chalk"));
738
- var import_commander2 = require("commander");
739
- var infoCommand = new import_commander2.Command("info").description("Display information about the current project").action(async () => {
740
- console.log(import_chalk4.default.bold("\n\u{1F4E6} Project Information\n"));
741
- if (!authExists()) {
742
- console.log(import_chalk4.default.yellow("Authentication: Not logged in"));
743
- console.log(import_chalk4.default.dim("Run `commet login` to authenticate\n"));
744
- return;
745
- }
746
- console.log(import_chalk4.default.green("Authentication: Logged in \u2713"));
747
- if (!projectConfigExists()) {
748
- console.log(import_chalk4.default.yellow("\nProject: Not linked"));
749
- console.log(
750
- import_chalk4.default.dim("Run `commet link` to connect to an organization\n")
751
- );
752
- return;
753
- }
754
- const projectConfig = loadProjectConfig();
755
- if (!projectConfig) {
756
- console.log(import_chalk4.default.red("\nProject: Invalid configuration"));
757
- return;
758
- }
759
- console.log(import_chalk4.default.green("\nProject: Linked \u2713"));
760
- console.log(import_chalk4.default.dim(" Organization:"), projectConfig.orgName);
761
- console.log(import_chalk4.default.dim(" Organization ID:"), projectConfig.orgId);
762
- console.log(import_chalk4.default.dim(" Mode:"), projectConfig.mode);
763
- console.log(
764
- import_chalk4.default.dim("\nRun `commet pull` to generate type definitions\n")
765
- );
766
- });
767
-
768
934
  // src/commands/link.ts
769
935
  var import_prompts2 = require("@inquirer/prompts");
770
- var import_chalk5 = __toESM(require("chalk"));
936
+ var import_chalk4 = __toESM(require("chalk"));
771
937
  var import_commander3 = require("commander");
772
938
  var import_ora3 = __toESM(require("ora"));
773
939
 
774
940
  // src/utils/gitignore-updater.ts
775
- var fs3 = __toESM(require("fs"));
776
- var path3 = __toESM(require("path"));
941
+ var fs4 = __toESM(require("fs"));
942
+ var path4 = __toESM(require("path"));
777
943
  function updateGitignore(entry) {
778
944
  const cwd = process.cwd();
779
- const gitignorePath = path3.join(cwd, ".gitignore");
945
+ const gitignorePath = path4.join(cwd, ".gitignore");
780
946
  try {
781
947
  let content = "";
782
- if (fs3.existsSync(gitignorePath)) {
783
- content = fs3.readFileSync(gitignorePath, "utf8");
948
+ if (fs4.existsSync(gitignorePath)) {
949
+ content = fs4.readFileSync(gitignorePath, "utf8");
784
950
  const lines = content.split("\n");
785
951
  for (const line of lines) {
786
952
  const trimmed = line.trim();
@@ -794,7 +960,7 @@ function updateGitignore(entry) {
794
960
  }
795
961
  content += `${entry}
796
962
  `;
797
- fs3.writeFileSync(gitignorePath, content, "utf8");
963
+ fs4.writeFileSync(gitignorePath, content, "utf8");
798
964
  return { success: true };
799
965
  } catch (error) {
800
966
  return {
@@ -805,20 +971,34 @@ function updateGitignore(entry) {
805
971
  }
806
972
 
807
973
  // src/commands/link.ts
808
- var linkCommand = new import_commander3.Command("link").description("Link this project to a Commet organization").action(async () => {
974
+ var linkCommand = new import_commander3.Command("link").description(
975
+ "Link this project directory to a Commet organization. Creates .commet/config.json with the selected org."
976
+ ).option(
977
+ "--org <slug-or-id>",
978
+ "Organization slug or ID \u2014 skips interactive selection"
979
+ ).addHelpText(
980
+ "after",
981
+ `
982
+ Examples:
983
+ $ commet link Interactive \u2014 choose from a list
984
+ $ commet link --org acme Non-interactive \u2014 match by slug or ID
985
+
986
+ Use 'commet orgs' to see available organizations and their slugs.
987
+ `
988
+ ).action(async (options) => {
809
989
  if (!authExists()) {
810
- console.log(import_chalk5.default.red("\u2717 Not authenticated"));
811
- console.log(import_chalk5.default.dim("Run `commet login` first"));
812
- return;
990
+ console.log(import_chalk4.default.red("\u2717 Not authenticated"));
991
+ console.log(import_chalk4.default.dim("Run `commet login` first"));
992
+ process.exit(1);
813
993
  }
814
994
  if (projectConfigExists()) {
815
995
  const config = loadProjectConfig();
816
- console.log(import_chalk5.default.yellow("\u26A0 This project is already linked"));
996
+ console.log(import_chalk4.default.yellow("\u26A0 This project is already linked"));
817
997
  console.log(
818
- import_chalk5.default.dim(`Organization: ${config?.orgName} \xB7 ${config?.mode}`)
998
+ import_chalk4.default.dim(`Organization: ${config?.orgName} \xB7 ${config?.mode}`)
819
999
  );
820
1000
  console.log(
821
- import_chalk5.default.dim(
1001
+ import_chalk4.default.dim(
822
1002
  "\nRun `commet unlink` first if you want to change the organization"
823
1003
  )
824
1004
  );
@@ -830,37 +1010,52 @@ var linkCommand = new import_commander3.Command("link").description("Link this p
830
1010
  );
831
1011
  if (result.error || !result.data) {
832
1012
  spinner.fail("Failed to fetch organizations");
833
- console.error(import_chalk5.default.red("Error:"), result.error);
834
- return;
1013
+ console.error(import_chalk4.default.red("Error:"), result.error);
1014
+ process.exit(1);
835
1015
  }
836
1016
  const { organizations } = result.data;
837
1017
  if (organizations.length === 0) {
838
1018
  spinner.stop();
839
- console.log(import_chalk5.default.yellow("\u26A0 No organizations found"));
1019
+ console.log(import_chalk4.default.yellow("\u26A0 No organizations found"));
840
1020
  console.log(
841
- import_chalk5.default.dim("Create an organization at https://commet.co first")
1021
+ import_chalk4.default.dim("Create an organization at https://commet.co first")
842
1022
  );
843
1023
  return;
844
1024
  }
845
1025
  spinner.stop();
846
- let orgId;
847
- try {
848
- orgId = await (0, import_prompts2.select)({
849
- message: "Select organization:",
850
- choices: organizations.map((org) => ({
851
- name: `${org.name} ${import_chalk5.default.dim(`(${org.slug}) \xB7 ${org.mode}`)}`,
852
- value: org.id
853
- })),
854
- theme: promptTheme
855
- });
856
- } catch (_error) {
857
- console.log(import_chalk5.default.yellow("\n\u26A0 Link cancelled"));
858
- return;
859
- }
860
- const selectedOrg = organizations.find((org) => org.id === orgId);
861
- if (!selectedOrg) {
862
- console.log(import_chalk5.default.red("\u2717 Organization not found"));
863
- return;
1026
+ let selectedOrg;
1027
+ if (options.org) {
1028
+ selectedOrg = organizations.find(
1029
+ (org) => org.slug === options.org || org.id === options.org
1030
+ );
1031
+ if (!selectedOrg) {
1032
+ console.log(import_chalk4.default.red(`\u2717 Organization "${options.org}" not found`));
1033
+ console.log(import_chalk4.default.dim("\nAvailable organizations:"));
1034
+ for (const org of organizations) {
1035
+ console.log(import_chalk4.default.dim(` ${org.slug} (${org.mode})`));
1036
+ }
1037
+ process.exit(1);
1038
+ }
1039
+ } else {
1040
+ let orgId;
1041
+ try {
1042
+ orgId = await (0, import_prompts2.select)({
1043
+ message: "Select organization:",
1044
+ choices: organizations.map((org) => ({
1045
+ name: `${org.name} ${import_chalk4.default.dim(`(${org.slug}) \xB7 ${org.mode}`)}`,
1046
+ value: org.id
1047
+ })),
1048
+ theme: promptTheme
1049
+ });
1050
+ } catch (_error) {
1051
+ console.log(import_chalk4.default.yellow("\n\u26A0 Link cancelled"));
1052
+ return;
1053
+ }
1054
+ selectedOrg = organizations.find((org) => org.id === orgId);
1055
+ if (!selectedOrg) {
1056
+ console.log(import_chalk4.default.red("\u2717 Organization not found"));
1057
+ process.exit(1);
1058
+ }
864
1059
  }
865
1060
  saveProjectConfig({
866
1061
  orgId: selectedOrg.id,
@@ -868,119 +1063,171 @@ var linkCommand = new import_commander3.Command("link").description("Link this p
868
1063
  mode: selectedOrg.mode
869
1064
  });
870
1065
  const gitignoreResult = updateGitignore(".commet/");
871
- console.log(import_chalk5.default.green("\n\u2713 Project linked successfully"));
1066
+ console.log(import_chalk4.default.green("\n\u2713 Project linked successfully"));
872
1067
  console.log(
873
- import_chalk5.default.dim(`Organization: ${selectedOrg.name} \xB7 ${selectedOrg.mode}`)
1068
+ import_chalk4.default.dim(`Organization: ${selectedOrg.name} \xB7 ${selectedOrg.mode}`)
874
1069
  );
875
1070
  if (gitignoreResult.success) {
876
- console.log(import_chalk5.default.green("\u2713 Updated .gitignore"));
1071
+ console.log(import_chalk4.default.green("\u2713 Updated .gitignore"));
877
1072
  } else {
878
- console.log(import_chalk5.default.yellow("\u26A0 No .gitignore found"));
879
- console.log(import_chalk5.default.dim("Add .commet/ to your .gitignore file"));
1073
+ console.log(import_chalk4.default.yellow("\u26A0 No .gitignore found"));
1074
+ console.log(import_chalk4.default.dim("Add .commet/ to your .gitignore file"));
880
1075
  }
881
- console.log(import_chalk5.default.dim("\nRun 'commet pull' to generate TypeScript types"));
1076
+ console.log(import_chalk4.default.dim("\nRun 'commet pull' to generate TypeScript types"));
882
1077
  });
883
1078
 
884
1079
  // src/commands/list.ts
885
- var import_chalk6 = __toESM(require("chalk"));
1080
+ var import_chalk5 = __toESM(require("chalk"));
886
1081
  var import_commander4 = require("commander");
887
1082
  var import_ora4 = __toESM(require("ora"));
888
1083
  var validTypes = ["features", "seats", "plans"];
889
- var listCommand = new import_commander4.Command("list").description("List features, seat types, or plans").argument("<type>", "Type to list (features, seats, or plans)").action(async (type) => {
1084
+ var listCommand = new import_commander4.Command("list").description(
1085
+ "List resources from your linked organization. Shows features, seat types, or plans currently configured on remote."
1086
+ ).argument("<type>", "What to list: features, seats, or plans").option("--json", "Output as JSON array").addHelpText(
1087
+ "after",
1088
+ `
1089
+ Examples:
1090
+ $ commet list features Show all features with type and description
1091
+ $ commet list plans Show all plans
1092
+ $ commet list seats Show all seat types
1093
+ $ commet list features --json JSON array for agent/CI use
1094
+ `
1095
+ ).action(async (type, options) => {
1096
+ const jsonMode = options.json;
890
1097
  if (!validTypes.includes(type)) {
891
- console.log(
892
- import_chalk6.default.red('\u2717 Invalid type. Use "features", "seats", or "plans"')
893
- );
894
- console.log(import_chalk6.default.dim("\nExamples:"));
895
- console.log(import_chalk6.default.dim(" commet list features"));
896
- console.log(import_chalk6.default.dim(" commet list seats"));
897
- console.log(import_chalk6.default.dim(" commet list plans"));
898
- return;
1098
+ if (jsonMode) {
1099
+ console.log(
1100
+ JSON.stringify({
1101
+ error: `Invalid type "${type}". Use: features, seats, or plans`
1102
+ })
1103
+ );
1104
+ } else {
1105
+ console.log(
1106
+ import_chalk5.default.red('\u2717 Invalid type. Use "features", "seats", or "plans"')
1107
+ );
1108
+ console.log(import_chalk5.default.dim("\nExamples:"));
1109
+ console.log(import_chalk5.default.dim(" commet list features"));
1110
+ console.log(import_chalk5.default.dim(" commet list seats"));
1111
+ console.log(import_chalk5.default.dim(" commet list plans"));
1112
+ }
1113
+ process.exit(1);
899
1114
  }
900
1115
  if (!authExists()) {
901
- console.log(import_chalk6.default.red("\u2717 Not authenticated"));
902
- console.log(import_chalk6.default.dim("Run `commet login` first"));
903
- return;
1116
+ if (jsonMode) {
1117
+ console.log(JSON.stringify({ error: "Not authenticated" }));
1118
+ } else {
1119
+ console.log(import_chalk5.default.red("\u2717 Not authenticated"));
1120
+ console.log(import_chalk5.default.dim("Run `commet login` first"));
1121
+ }
1122
+ process.exit(1);
904
1123
  }
905
1124
  if (!projectConfigExists()) {
906
- console.log(import_chalk6.default.red("\u2717 Project not linked"));
907
- console.log(
908
- import_chalk6.default.dim("Run `commet link` first to connect to an organization")
909
- );
910
- return;
1125
+ if (jsonMode) {
1126
+ console.log(JSON.stringify({ error: "Project not linked" }));
1127
+ } else {
1128
+ console.log(import_chalk5.default.red("\u2717 Project not linked"));
1129
+ console.log(
1130
+ import_chalk5.default.dim("Run `commet link` first to connect to an organization")
1131
+ );
1132
+ }
1133
+ process.exit(1);
911
1134
  }
912
1135
  const projectConfig = loadProjectConfig();
913
1136
  if (!projectConfig) {
914
- console.log(import_chalk6.default.red("\u2717 Invalid project configuration"));
915
- return;
1137
+ if (jsonMode) {
1138
+ console.log(JSON.stringify({ error: "Invalid project configuration" }));
1139
+ } else {
1140
+ console.log(import_chalk5.default.red("\u2717 Invalid project configuration"));
1141
+ }
1142
+ process.exit(1);
916
1143
  }
917
- const spinner = (0, import_ora4.default)(`Fetching ${type}...`).start();
1144
+ const spinner = jsonMode ? null : (0, import_ora4.default)(`Fetching ${type}...`).start();
918
1145
  const result = await apiRequest(
919
- `${BASE_URL}/api/cli/types?orgId=${projectConfig.orgId}`
1146
+ `${BASE_URL}/api/cli/pull?orgId=${projectConfig.orgId}`
920
1147
  );
921
1148
  if (result.error || !result.data) {
922
- spinner.fail(`Failed to fetch ${type}`);
923
- console.error(import_chalk6.default.red("Error:"), result.error);
924
- return;
1149
+ if (jsonMode) {
1150
+ console.log(JSON.stringify({ error: result.error }));
1151
+ } else {
1152
+ spinner?.fail(`Failed to fetch ${type}`);
1153
+ console.error(import_chalk5.default.red("Error:"), result.error);
1154
+ }
1155
+ process.exit(1);
925
1156
  }
926
- spinner.stop();
1157
+ spinner?.stop();
927
1158
  if (type === "features") {
928
1159
  const { features } = result.data;
1160
+ if (jsonMode) {
1161
+ console.log(JSON.stringify(features));
1162
+ return;
1163
+ }
929
1164
  if (features.length === 0) {
930
- console.log(import_chalk6.default.yellow("\u26A0 No features found"));
1165
+ console.log(import_chalk5.default.yellow("\u26A0 No features found"));
931
1166
  console.log(
932
- import_chalk6.default.dim("Create features in your Commet dashboard first")
1167
+ import_chalk5.default.dim("Create features in your Commet dashboard first")
933
1168
  );
934
1169
  return;
935
1170
  }
936
- console.log(import_chalk6.default.bold(`
937
- \u{1F4CA} Features (${features.length})
1171
+ console.log(import_chalk5.default.bold(`
1172
+ Features (${features.length})
938
1173
  `));
939
1174
  for (const feature of features) {
940
- console.log(import_chalk6.default.green(`\u2022 ${feature.code} (${feature.type})`));
941
- console.log(import_chalk6.default.dim(` ${feature.name}`));
1175
+ console.log(
1176
+ import_chalk5.default.green(` ${feature.code} ${import_chalk5.default.dim(`(${feature.type})`)}`)
1177
+ );
1178
+ console.log(import_chalk5.default.dim(` ${feature.name}`));
942
1179
  if (feature.description) {
943
- console.log(import_chalk6.default.dim(` ${feature.description}`));
1180
+ console.log(import_chalk5.default.dim(` ${feature.description}`));
944
1181
  }
945
1182
  console.log("");
946
1183
  }
947
1184
  } else if (type === "seats") {
948
1185
  const { seatTypes } = result.data;
1186
+ if (jsonMode) {
1187
+ console.log(JSON.stringify(seatTypes));
1188
+ return;
1189
+ }
949
1190
  if (seatTypes.length === 0) {
950
- console.log(import_chalk6.default.yellow("\u26A0 No seat types found"));
1191
+ console.log(import_chalk5.default.yellow("\u26A0 No seat types found"));
951
1192
  console.log(
952
- import_chalk6.default.dim("Create seat types in your Commet dashboard first")
1193
+ import_chalk5.default.dim("Create seat types in your Commet dashboard first")
953
1194
  );
954
1195
  return;
955
1196
  }
956
- console.log(import_chalk6.default.bold(`
957
- \u{1F4BA} Seat Types (${seatTypes.length})
1197
+ console.log(import_chalk5.default.bold(`
1198
+ Seat Types (${seatTypes.length})
958
1199
  `));
959
1200
  for (const seatType of seatTypes) {
960
1201
  console.log(
961
- import_chalk6.default.green(`\u2022 ${seatType.code}${seatType.isFree ? " (Free)" : ""}`)
1202
+ import_chalk5.default.green(
1203
+ ` ${seatType.code}${seatType.isFree ? import_chalk5.default.dim(" (free)") : ""}`
1204
+ )
962
1205
  );
963
- console.log(import_chalk6.default.dim(` ${seatType.name}`));
1206
+ console.log(import_chalk5.default.dim(` ${seatType.name}`));
964
1207
  if (seatType.description) {
965
- console.log(import_chalk6.default.dim(` ${seatType.description}`));
1208
+ console.log(import_chalk5.default.dim(` ${seatType.description}`));
966
1209
  }
967
1210
  console.log("");
968
1211
  }
969
1212
  } else {
970
1213
  const { plans } = result.data;
1214
+ if (jsonMode) {
1215
+ console.log(JSON.stringify(plans));
1216
+ return;
1217
+ }
971
1218
  if (plans.length === 0) {
972
- console.log(import_chalk6.default.yellow("\u26A0 No plans found"));
973
- console.log(import_chalk6.default.dim("Create plans in your Commet dashboard first"));
1219
+ console.log(import_chalk5.default.yellow("\u26A0 No plans found"));
1220
+ console.log(import_chalk5.default.dim("Create plans in your Commet dashboard first"));
974
1221
  return;
975
1222
  }
976
- console.log(import_chalk6.default.bold(`
977
- \u{1F4CB} Plans (${plans.length})
1223
+ console.log(import_chalk5.default.bold(`
1224
+ Plans (${plans.length})
978
1225
  `));
979
1226
  for (const plan of plans) {
980
- console.log(import_chalk6.default.green(`\u2022 ${plan.code}`));
981
- console.log(import_chalk6.default.dim(` ${plan.name}`));
1227
+ console.log(import_chalk5.default.green(` ${plan.code}`));
1228
+ console.log(import_chalk5.default.dim(` ${plan.name}`));
982
1229
  if (plan.description) {
983
- console.log(import_chalk6.default.dim(` ${plan.description}`));
1230
+ console.log(import_chalk5.default.dim(` ${plan.description}`));
984
1231
  }
985
1232
  console.log("");
986
1233
  }
@@ -989,21 +1236,21 @@ var listCommand = new import_commander4.Command("list").description("List featur
989
1236
 
990
1237
  // src/commands/listen.ts
991
1238
  var import_ably = __toESM(require("ably"));
992
- var import_chalk7 = __toESM(require("chalk"));
1239
+ var import_chalk6 = __toESM(require("chalk"));
993
1240
  var import_commander5 = require("commander");
994
1241
  function printEventLine(line) {
995
1242
  const time = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
996
- const eventName = import_chalk7.default.yellow(line.event.padEnd(28));
997
- const timing = import_chalk7.default.dim(`(${line.ms}ms)`);
1243
+ const eventName = import_chalk6.default.yellow(line.event.padEnd(28));
1244
+ const timing = import_chalk6.default.dim(`(${line.ms}ms)`);
998
1245
  if ("error" in line) {
999
1246
  console.log(
1000
- ` ${import_chalk7.default.dim(time)} ${eventName} \u2192 ${import_chalk7.default.red("Error")} ${timing}`
1247
+ ` ${import_chalk6.default.dim(time)} ${eventName} \u2192 ${import_chalk6.default.red("Error")} ${timing}`
1001
1248
  );
1002
- console.log(` ${" ".repeat(12)}${import_chalk7.default.red(line.error)}`);
1249
+ console.log(` ${" ".repeat(12)}${import_chalk6.default.red(line.error)}`);
1003
1250
  return;
1004
1251
  }
1005
- const status = line.statusCode < 400 ? import_chalk7.default.green(`${line.statusCode} OK`) : import_chalk7.default.red(`${line.statusCode} Error`);
1006
- console.log(` ${import_chalk7.default.dim(time)} ${eventName} \u2192 ${status} ${timing}`);
1252
+ const status = line.statusCode < 400 ? import_chalk6.default.green(`${line.statusCode} OK`) : import_chalk6.default.red(`${line.statusCode} Error`);
1253
+ console.log(` ${import_chalk6.default.dim(time)} ${eventName} \u2192 ${status} ${timing}`);
1007
1254
  }
1008
1255
  function isListenMessage(data) {
1009
1256
  if (typeof data !== "object" || data === null) return false;
@@ -1026,18 +1273,31 @@ function resolveTargetUrl(input2) {
1026
1273
  }
1027
1274
  return parsed.toString();
1028
1275
  }
1029
- var listenCommand = new import_commander5.Command("listen").description("Forward webhook events to your local server").argument(
1276
+ var listenCommand = new import_commander5.Command("listen").description(
1277
+ "Forward webhook events from Commet to your local server in real time. Opens a persistent connection and replays every event as an HTTP POST to your URL."
1278
+ ).argument(
1030
1279
  "<url>",
1031
- "URL to forward to (e.g. localhost:3000, local.commet.co:3010/webhooks, or just a port)"
1032
- ).option("--events <types>", "Comma-separated event types to filter").action(async (url, options) => {
1280
+ "Target URL \u2014 a port (3000), host:port (localhost:3000), or full URL"
1281
+ ).option(
1282
+ "--events <types>",
1283
+ "Only forward these event types (comma-separated)"
1284
+ ).addHelpText(
1285
+ "after",
1286
+ `
1287
+ Examples:
1288
+ $ commet listen 3000 Forward to http://localhost:3000/
1289
+ $ commet listen localhost:3000/webhooks Forward to a specific path
1290
+ $ commet listen 3000 --events invoice.paid Only forward invoice.paid events
1291
+ `
1292
+ ).action(async (url, options) => {
1033
1293
  const auth = loadAuth();
1034
1294
  if (!auth) {
1035
- console.log(import_chalk7.default.red("Not authenticated. Run: commet login"));
1295
+ console.log(import_chalk6.default.red("Not authenticated. Run: commet login"));
1036
1296
  process.exit(1);
1037
1297
  }
1038
1298
  const projectConfig = loadProjectConfig();
1039
1299
  if (!projectConfig) {
1040
- console.log(import_chalk7.default.red("No project linked. Run: commet link"));
1300
+ console.log(import_chalk6.default.red("No project linked. Run: commet link"));
1041
1301
  process.exit(1);
1042
1302
  }
1043
1303
  let targetUrl;
@@ -1045,7 +1305,7 @@ var listenCommand = new import_commander5.Command("listen").description("Forward
1045
1305
  targetUrl = resolveTargetUrl(url);
1046
1306
  } catch (error) {
1047
1307
  console.log(
1048
- import_chalk7.default.red(error instanceof Error ? error.message : "Invalid URL")
1308
+ import_chalk6.default.red(error instanceof Error ? error.message : "Invalid URL")
1049
1309
  );
1050
1310
  process.exit(1);
1051
1311
  }
@@ -1058,9 +1318,9 @@ var listenCommand = new import_commander5.Command("listen").description("Forward
1058
1318
  }
1059
1319
  );
1060
1320
  if (result.error || !result.data) {
1061
- console.log(import_chalk7.default.red("Failed to start listen session"));
1321
+ console.log(import_chalk6.default.red("Failed to start listen session"));
1062
1322
  if (result.error) {
1063
- console.log(import_chalk7.default.dim(result.error));
1323
+ console.log(import_chalk6.default.dim(result.error));
1064
1324
  }
1065
1325
  process.exit(1);
1066
1326
  }
@@ -1106,27 +1366,27 @@ var listenCommand = new import_commander5.Command("listen").description("Forward
1106
1366
  let wasConnected = false;
1107
1367
  ably.connection.on("connected", () => {
1108
1368
  if (wasConnected) {
1109
- console.log(import_chalk7.default.green(" \u2713 Reconnected"));
1369
+ console.log(import_chalk6.default.green(" \u2713 Reconnected"));
1110
1370
  }
1111
1371
  wasConnected = true;
1112
1372
  });
1113
1373
  ably.connection.on("disconnected", () => {
1114
- console.log(import_chalk7.default.yellow("\n \u26A0 Disconnected. Reconnecting..."));
1374
+ console.log(import_chalk6.default.yellow("\n \u26A0 Disconnected. Reconnecting..."));
1115
1375
  });
1116
1376
  ably.connection.on("failed", () => {
1117
1377
  console.log(
1118
- import_chalk7.default.red("\n \u2717 Connection failed. Check your authentication.")
1378
+ import_chalk6.default.red("\n \u2717 Connection failed. Check your authentication.")
1119
1379
  );
1120
1380
  process.exit(1);
1121
1381
  });
1122
1382
  const channel = ably.channels.get(channelName);
1123
1383
  console.log("");
1124
1384
  console.log(
1125
- import_chalk7.default.green(` \u2713 Authenticated (org: ${projectConfig.orgName})`)
1385
+ import_chalk6.default.green(` \u2713 Authenticated (org: ${projectConfig.orgName})`)
1126
1386
  );
1127
- console.log(import_chalk7.default.green(" \u2713 Connected to Commet webhook stream"));
1128
- console.log(import_chalk7.default.cyan(` \u27F6 Forwarding to ${targetUrl}`));
1129
- console.log(import_chalk7.default.dim(` \u27F6 Signing secret: ${signingSecret}`));
1387
+ console.log(import_chalk6.default.green(" \u2713 Connected to Commet webhook stream"));
1388
+ console.log(import_chalk6.default.cyan(` \u27F6 Forwarding to ${targetUrl}`));
1389
+ console.log(import_chalk6.default.dim(` \u27F6 Signing secret: ${signingSecret}`));
1130
1390
  console.log("");
1131
1391
  console.log(" Ready! Listening for webhook events...");
1132
1392
  console.log("");
@@ -1157,7 +1417,7 @@ var listenCommand = new import_commander5.Command("listen").description("Forward
1157
1417
  process.on("SIGINT", async () => {
1158
1418
  if (isShuttingDown) return;
1159
1419
  isShuttingDown = true;
1160
- console.log(import_chalk7.default.dim("\n Disconnecting..."));
1420
+ console.log(import_chalk6.default.dim("\n Disconnecting..."));
1161
1421
  ably.close();
1162
1422
  await apiRequest(`${BASE_URL}/api/cli/listen/stop`, {
1163
1423
  method: "POST",
@@ -1171,13 +1431,15 @@ var listenCommand = new import_commander5.Command("listen").description("Forward
1171
1431
  });
1172
1432
 
1173
1433
  // src/commands/login.ts
1174
- var import_chalk8 = __toESM(require("chalk"));
1434
+ var import_chalk7 = __toESM(require("chalk"));
1175
1435
  var import_commander6 = require("commander");
1176
- var loginCommand = new import_commander6.Command("login").description("Authenticate with Commet").action(async () => {
1436
+ var loginCommand = new import_commander6.Command("login").description(
1437
+ "Authenticate with Commet via browser. Opens a device-code flow \u2014 you confirm in the browser and the CLI stores your token locally at ~/.commet/auth.json."
1438
+ ).action(async () => {
1177
1439
  if (authExists()) {
1178
- console.log(import_chalk8.default.yellow("\u26A0 You are already logged in."));
1440
+ console.log(import_chalk7.default.yellow("\u26A0 You are already logged in."));
1179
1441
  console.log(
1180
- import_chalk8.default.dim(
1442
+ import_chalk7.default.dim(
1181
1443
  "Run `commet logout` first if you want to login with a different account."
1182
1444
  )
1183
1445
  );
@@ -1185,9 +1447,9 @@ var loginCommand = new import_commander6.Command("login").description("Authentic
1185
1447
  }
1186
1448
  const success = await performLogin();
1187
1449
  if (success) {
1188
- console.log(import_chalk8.default.green("\n\u2713 Authentication complete"));
1450
+ console.log(import_chalk7.default.green("\n\u2713 Authentication complete"));
1189
1451
  console.log(
1190
- import_chalk8.default.dim(
1452
+ import_chalk7.default.dim(
1191
1453
  "\nNext steps:\n 1. Run `commet link` to connect a project\n 2. Run `commet pull` to generate types\n"
1192
1454
  )
1193
1455
  );
@@ -1195,345 +1457,919 @@ var loginCommand = new import_commander6.Command("login").description("Authentic
1195
1457
  });
1196
1458
 
1197
1459
  // src/commands/logout.ts
1198
- var import_chalk9 = __toESM(require("chalk"));
1460
+ var import_chalk8 = __toESM(require("chalk"));
1199
1461
  var import_commander7 = require("commander");
1200
- var logoutCommand = new import_commander7.Command("logout").description("Log out of Commet").action(async () => {
1462
+ var logoutCommand = new import_commander7.Command("logout").description(
1463
+ "Log out and remove stored credentials from ~/.commet/auth.json."
1464
+ ).action(async () => {
1201
1465
  if (!authExists()) {
1202
- console.log(import_chalk9.default.yellow("\u26A0 You are not logged in."));
1466
+ console.log(import_chalk8.default.yellow("\u26A0 You are not logged in."));
1203
1467
  return;
1204
1468
  }
1205
1469
  clearAuth();
1206
- console.log(import_chalk9.default.green("\u2713 Successfully logged out"));
1470
+ console.log(import_chalk8.default.green("\u2713 Successfully logged out"));
1207
1471
  });
1208
1472
 
1209
- // src/commands/pull.ts
1210
- var fs6 = __toESM(require("fs"));
1211
- var path6 = __toESM(require("path"));
1212
- var import_chalk10 = __toESM(require("chalk"));
1473
+ // src/commands/orgs.ts
1474
+ var import_chalk9 = __toESM(require("chalk"));
1213
1475
  var import_commander8 = require("commander");
1214
1476
  var import_ora5 = __toESM(require("ora"));
1477
+ var orgsCommand = new import_commander8.Command("orgs").description(
1478
+ "List all organizations you have access to. Shows name, slug, mode (live/sandbox), and which one is currently linked."
1479
+ ).option("--json", "Output as JSON array").addHelpText(
1480
+ "after",
1481
+ `
1482
+ Examples:
1483
+ $ commet orgs Show orgs with current selection marked
1484
+ $ commet orgs --json JSON array for agent/CI use
1215
1485
 
1216
- // src/utils/environment-validator.ts
1217
- var fs4 = __toESM(require("fs"));
1218
- var path4 = __toESM(require("path"));
1219
- function validateTypeScriptProject() {
1220
- const cwd = process.cwd();
1221
- const tsconfigPath = path4.join(cwd, "tsconfig.json");
1222
- if (!fs4.existsSync(tsconfigPath)) {
1223
- return false;
1486
+ The slug shown here is what you pass to 'commet link --org <slug>'.
1487
+ `
1488
+ ).action(async (options) => {
1489
+ const jsonMode = options.json;
1490
+ if (!authExists()) {
1491
+ if (jsonMode) {
1492
+ console.log(JSON.stringify({ error: "Not authenticated" }));
1493
+ } else {
1494
+ console.log(import_chalk9.default.red("\u2717 Not authenticated"));
1495
+ console.log(import_chalk9.default.dim("Run `commet login` first"));
1496
+ }
1497
+ process.exit(1);
1224
1498
  }
1225
- try {
1226
- const content = fs4.readFileSync(tsconfigPath, "utf8");
1227
- JSON.parse(content);
1228
- return true;
1229
- } catch (_error) {
1230
- return false;
1499
+ const spinner = jsonMode ? null : (0, import_ora5.default)("Fetching organizations...").start();
1500
+ const result = await apiRequest(
1501
+ `${BASE_URL}/api/cli/organizations`
1502
+ );
1503
+ if (result.error || !result.data) {
1504
+ if (jsonMode) {
1505
+ console.log(JSON.stringify({ error: result.error }));
1506
+ } else {
1507
+ spinner?.fail("Failed to fetch organizations");
1508
+ console.error(import_chalk9.default.red("Error:"), result.error);
1509
+ }
1510
+ process.exit(1);
1231
1511
  }
1232
- }
1512
+ spinner?.stop();
1513
+ const { organizations } = result.data;
1514
+ if (jsonMode) {
1515
+ console.log(JSON.stringify(organizations));
1516
+ return;
1517
+ }
1518
+ if (organizations.length === 0) {
1519
+ console.log(import_chalk9.default.yellow("\u26A0 No organizations found"));
1520
+ console.log(
1521
+ import_chalk9.default.dim("Create an organization at https://commet.co first")
1522
+ );
1523
+ return;
1524
+ }
1525
+ const currentProject = loadProjectConfig();
1526
+ console.log(import_chalk9.default.bold(`
1527
+ Organizations (${organizations.length})
1528
+ `));
1529
+ for (const org of organizations) {
1530
+ const isCurrent = currentProject?.orgId === org.id;
1531
+ const marker = isCurrent ? import_chalk9.default.green("\u25CF") : import_chalk9.default.dim("\u25CB");
1532
+ const mode = org.mode === "live" ? import_chalk9.default.green(org.mode) : import_chalk9.default.yellow(org.mode);
1533
+ console.log(
1534
+ ` ${marker} ${org.name} ${import_chalk9.default.dim(`(${org.slug})`)} ${mode}`
1535
+ );
1536
+ }
1537
+ console.log("");
1538
+ });
1233
1539
 
1234
- // src/utils/generator.ts
1235
- function generateTypes(seatTypes, features, plans) {
1236
- const seatTypeUnion = seatTypes.length > 0 ? seatTypes.map((s) => `"${s.code}"`).join(" | ") : "string";
1237
- const planCodeUnion = plans.length > 0 ? plans.map((p) => `"${p.code}"`).join(" | ") : "string";
1238
- const featureCodeUnion = features.length > 0 ? features.map((f) => `"${f.code}"`).join(" | ") : "string";
1239
- const seatComments = seatTypes.map(
1240
- (s) => ` * - "${s.code}": ${s.name}${s.description ? ` - ${s.description}` : ""} ${s.isFree ? "(Free)" : ""}`
1241
- ).join("\n");
1242
- const planComments = plans.map(
1243
- (p) => ` * - "${p.code}": ${p.name}${p.description ? ` - ${p.description}` : ""}`
1244
- ).join("\n");
1245
- const featureComments = features.map(
1246
- (f) => ` * - "${f.code}": ${f.name} (${f.type})${f.description ? ` - ${f.description}` : ""}`
1247
- ).join("\n");
1248
- return `// Auto-generated by Commet CLI
1249
- // Do not edit this file manually - run 'commet pull' to update
1540
+ // src/commands/pull.ts
1541
+ var fs5 = __toESM(require("fs"));
1542
+ var path5 = __toESM(require("path"));
1543
+ var import_prompts3 = require("@inquirer/prompts");
1544
+ var import_chalk11 = __toESM(require("chalk"));
1545
+ var import_commander9 = require("commander");
1546
+ var import_ora6 = __toESM(require("ora"));
1250
1547
 
1251
- /**
1252
- * This augments the Commet SDK with your organization's
1253
- * feature codes, seat types, and plan codes for autocomplete.
1254
- *
1255
- * Features available in your organization:
1256
- ${featureComments || " * (none)"}
1257
- *
1258
- * Seat types available in your organization:
1259
- ${seatComments || " * (none)"}
1260
- *
1261
- * Plans available in your organization:
1262
- ${planComments || " * (none)"}
1263
- *
1264
- */
1265
- declare module '@commet/node' {
1266
- interface CommetGeneratedTypes {
1267
- seatType: ${seatTypeUnion};
1268
- planCode: ${planCodeUnion};
1269
- featureCode: ${featureCodeUnion};
1548
+ // src/utils/diff.ts
1549
+ var import_chalk10 = __toESM(require("chalk"));
1550
+ function computeDiff(config, remote) {
1551
+ const remoteFeatureMap = new Map(remote.features.map((f) => [f.code, f]));
1552
+ const remotePlanMap = new Map(remote.plans.map((p) => [p.code, p]));
1553
+ const featureChanges = [];
1554
+ for (const [code, localFeature] of Object.entries(config.features)) {
1555
+ const remoteFeature = remoteFeatureMap.get(code);
1556
+ if (!remoteFeature) {
1557
+ featureChanges.push({ code, action: "create" });
1558
+ continue;
1559
+ }
1560
+ const changes = [];
1561
+ if (remoteFeature.name !== localFeature.name) {
1562
+ changes.push(`name: "${remoteFeature.name}" \u2192 "${localFeature.name}"`);
1563
+ }
1564
+ if (remoteFeature.type !== localFeature.type) {
1565
+ changes.push(
1566
+ `type: "${remoteFeature.type}" \u2192 "${localFeature.type}" (BLOCKED)`
1567
+ );
1568
+ }
1569
+ if ("unitName" in localFeature && (remoteFeature.unitName ?? void 0) !== localFeature.unitName) {
1570
+ changes.push(
1571
+ `unitName: "${remoteFeature.unitName ?? ""}" \u2192 "${localFeature.unitName}"`
1572
+ );
1573
+ }
1574
+ featureChanges.push(
1575
+ changes.length > 0 ? { code, action: "update", changes } : { code, action: "unchanged" }
1576
+ );
1270
1577
  }
1578
+ const unmanagedFeatures = remote.features.filter((f) => !config.features[f.code]).map((f) => f.code);
1579
+ const planChanges = [];
1580
+ for (const [code, localPlan] of Object.entries(config.plans)) {
1581
+ const remotePlan = remotePlanMap.get(code);
1582
+ if (!remotePlan) {
1583
+ planChanges.push({ code, action: "create" });
1584
+ continue;
1585
+ }
1586
+ const changes = [];
1587
+ if (remotePlan.name !== localPlan.name) {
1588
+ changes.push(`name: "${remotePlan.name}" \u2192 "${localPlan.name}"`);
1589
+ }
1590
+ const remoteDefaultInterval = remotePlan.defaultInterval ?? remotePlan.prices.find((p) => p.isDefault)?.billingInterval ?? null;
1591
+ if (localPlan.defaultInterval && remoteDefaultInterval !== localPlan.defaultInterval) {
1592
+ changes.push(
1593
+ `defaultInterval: "${remoteDefaultInterval ?? "none"}" \u2192 "${localPlan.defaultInterval}"`
1594
+ );
1595
+ }
1596
+ const localPriceMap = new Map(localPlan.prices.map((p) => [p.interval, p]));
1597
+ const remotePriceMap = new Map(
1598
+ remotePlan.prices.map((p) => [p.billingInterval, p])
1599
+ );
1600
+ for (const [interval, localPrice] of localPriceMap) {
1601
+ const remotePrice = remotePriceMap.get(interval);
1602
+ if (!remotePrice) {
1603
+ changes.push(
1604
+ `price ${interval}: new ($${(localPrice.amount / 1e4).toFixed(2)})`
1605
+ );
1606
+ } else if (remotePrice.price !== localPrice.amount) {
1607
+ changes.push(
1608
+ `price ${interval}: $${(remotePrice.price / 1e4).toFixed(2)} \u2192 $${(localPrice.amount / 1e4).toFixed(2)}`
1609
+ );
1610
+ }
1611
+ }
1612
+ if (localPlan.features) {
1613
+ const remotePlanFeatureMap = new Map(
1614
+ remotePlan.features.map((f) => [f.featureCode, f])
1615
+ );
1616
+ for (const featureCode of Object.keys(localPlan.features)) {
1617
+ if (!remotePlanFeatureMap.has(featureCode)) {
1618
+ changes.push(`feature ${featureCode}: new`);
1619
+ }
1620
+ }
1621
+ }
1622
+ planChanges.push(
1623
+ changes.length > 0 ? { code, action: "update", changes } : { code, action: "unchanged" }
1624
+ );
1625
+ }
1626
+ const unmanagedPlans = remote.plans.filter((p) => !config.plans[p.code]).map((p) => p.code);
1627
+ const hasChanges = featureChanges.some((c) => c.action !== "unchanged") || planChanges.some((c) => c.action !== "unchanged");
1628
+ return {
1629
+ features: { changes: featureChanges, unmanaged: unmanagedFeatures },
1630
+ plans: { changes: planChanges, unmanaged: unmanagedPlans },
1631
+ hasChanges
1632
+ };
1271
1633
  }
1272
-
1273
- export {};
1274
- `;
1634
+ function formatDiff(diff) {
1635
+ const lines = [];
1636
+ lines.push(import_chalk10.default.bold("\nFeatures:"));
1637
+ for (const change of diff.features.changes) {
1638
+ if (change.action === "create") {
1639
+ lines.push(import_chalk10.default.green(` + ${change.code}`));
1640
+ } else if (change.action === "update") {
1641
+ lines.push(import_chalk10.default.yellow(` ~ ${change.code}`));
1642
+ for (const c of change.changes ?? []) {
1643
+ lines.push(import_chalk10.default.dim(` ${c}`));
1644
+ }
1645
+ } else {
1646
+ lines.push(import_chalk10.default.dim(` ${change.code}`));
1647
+ }
1648
+ }
1649
+ if (diff.features.unmanaged.length > 0) {
1650
+ lines.push(
1651
+ import_chalk10.default.dim(
1652
+ ` ? unmanaged: ${diff.features.unmanaged.join(", ")} (not in config, left as-is)`
1653
+ )
1654
+ );
1655
+ }
1656
+ lines.push(import_chalk10.default.bold("\nPlans:"));
1657
+ for (const change of diff.plans.changes) {
1658
+ if (change.action === "create") {
1659
+ lines.push(import_chalk10.default.green(` + ${change.code}`));
1660
+ } else if (change.action === "update") {
1661
+ lines.push(import_chalk10.default.yellow(` ~ ${change.code}`));
1662
+ for (const c of change.changes ?? []) {
1663
+ lines.push(import_chalk10.default.dim(` ${c}`));
1664
+ }
1665
+ } else {
1666
+ lines.push(import_chalk10.default.dim(` ${change.code}`));
1667
+ }
1668
+ }
1669
+ if (diff.plans.unmanaged.length > 0) {
1670
+ lines.push(
1671
+ import_chalk10.default.dim(
1672
+ ` ? unmanaged: ${diff.plans.unmanaged.join(", ")} (not in config, left as-is)`
1673
+ )
1674
+ );
1675
+ }
1676
+ return lines.join("\n");
1275
1677
  }
1276
1678
 
1277
- // src/utils/tsconfig-updater.ts
1278
- var fs5 = __toESM(require("fs"));
1279
- var path5 = __toESM(require("path"));
1280
- var import_jsonc_parser = require("jsonc-parser");
1281
- function updateTsConfig(entry) {
1282
- const cwd = process.cwd();
1283
- const tsconfigPath = path5.join(cwd, "tsconfig.json");
1284
- try {
1285
- if (!fs5.existsSync(tsconfigPath)) {
1286
- return {
1287
- success: false,
1288
- error: "tsconfig.json not found"
1289
- };
1290
- }
1291
- const content = fs5.readFileSync(tsconfigPath, "utf8");
1292
- const config = (0, import_jsonc_parser.parse)(content);
1293
- if (config.include && Array.isArray(config.include)) {
1294
- if (config.include.includes(entry)) {
1295
- return { success: true };
1679
+ // src/utils/generator.ts
1680
+ function generateConfigFile(features, plans) {
1681
+ const lines = [];
1682
+ lines.push('import { defineConfig } from "@commet/node";');
1683
+ lines.push("");
1684
+ lines.push("export default defineConfig({");
1685
+ lines.push(" features: {");
1686
+ for (const f of features) {
1687
+ const parts = [`name: "${f.name}"`, `type: "${f.type}"`];
1688
+ if (f.unitName) parts.push(`unitName: "${f.unitName}"`);
1689
+ if (f.description) parts.push(`description: "${f.description}"`);
1690
+ lines.push(` ${f.code}: { ${parts.join(", ")} },`);
1691
+ }
1692
+ lines.push(" },");
1693
+ lines.push(" plans: {");
1694
+ for (const p of plans) {
1695
+ lines.push(` ${p.code}: {`);
1696
+ lines.push(` name: "${p.name}",`);
1697
+ if (p.description) lines.push(` description: "${p.description}",`);
1698
+ if (p.consumptionModel)
1699
+ lines.push(` consumptionModel: "${p.consumptionModel}",`);
1700
+ if (p.isFree) lines.push(" isFree: true,");
1701
+ if (p.isPublic === false) lines.push(" isPublic: false,");
1702
+ if (p.sortOrder != null && p.sortOrder !== 0)
1703
+ lines.push(` sortOrder: ${p.sortOrder},`);
1704
+ const prices = p.prices ?? [];
1705
+ const defaultInterval = p.defaultInterval ?? prices.find((pr) => pr.isDefault)?.billingInterval ?? prices[0]?.billingInterval;
1706
+ if (defaultInterval)
1707
+ lines.push(` defaultInterval: "${defaultInterval}",`);
1708
+ if (prices.length === 0) {
1709
+ lines.push(" prices: [],");
1710
+ } else {
1711
+ lines.push(" prices: [");
1712
+ for (const price of prices) {
1713
+ const priceParts = [
1714
+ `interval: "${price.billingInterval}"`,
1715
+ `amount: ${price.price}`
1716
+ ];
1717
+ if (price.trialDays) priceParts.push(`trialDays: ${price.trialDays}`);
1718
+ lines.push(` { ${priceParts.join(", ")} },`);
1296
1719
  }
1720
+ lines.push(" ],");
1297
1721
  }
1298
- const currentInclude = config.include || [];
1299
- const newInclude = [...currentInclude, entry];
1300
- const edits = (0, import_jsonc_parser.modify)(content, ["include"], newInclude, {
1301
- formattingOptions: {
1302
- insertSpaces: true,
1303
- tabSize: 2
1722
+ const planFeatures = p.features ?? [];
1723
+ if (planFeatures.length > 0) {
1724
+ lines.push(" features: {");
1725
+ for (const pf of planFeatures) {
1726
+ const featureDef = features.find((f) => f.code === pf.featureCode);
1727
+ const isBoolean = featureDef?.type === "boolean";
1728
+ if (isBoolean) {
1729
+ lines.push(` ${pf.featureCode}: ${pf.enabled ?? true},`);
1730
+ } else {
1731
+ const parts = [];
1732
+ if (pf.includedAmount) parts.push(`included: ${pf.includedAmount}`);
1733
+ if (pf.unlimited) parts.push("unlimited: true");
1734
+ if (pf.overageEnabled && pf.overageUnitPrice) {
1735
+ parts.push(`overage: { unitPrice: ${pf.overageUnitPrice} }`);
1736
+ }
1737
+ if (parts.length > 0) {
1738
+ lines.push(` ${pf.featureCode}: { ${parts.join(", ")} },`);
1739
+ } else {
1740
+ lines.push(` ${pf.featureCode}: {},`);
1741
+ }
1742
+ }
1304
1743
  }
1305
- });
1306
- const updatedContent = (0, import_jsonc_parser.applyEdits)(content, edits);
1307
- fs5.writeFileSync(tsconfigPath, updatedContent, "utf8");
1308
- return { success: true };
1309
- } catch (error) {
1310
- return {
1311
- success: false,
1312
- error: error instanceof Error ? error.message : "Unknown error updating tsconfig.json"
1313
- };
1744
+ lines.push(" },");
1745
+ }
1746
+ lines.push(" },");
1314
1747
  }
1748
+ lines.push(" },");
1749
+ lines.push("});");
1750
+ lines.push("");
1751
+ return lines.join("\n");
1315
1752
  }
1316
1753
 
1317
1754
  // src/commands/pull.ts
1318
- var pullCommand = new import_commander8.Command("pull").description("Pull type definitions from Commet").action(async () => {
1755
+ var pullCommand = new import_commander9.Command("pull").description(
1756
+ "Fetch your billing config from Commet and generate (or update) commet.config.ts with features and plans."
1757
+ ).option("-y, --yes", "Skip confirmation prompt").option("--dry-run", "Show what would change without writing any files").option("--json", "Output structured JSON (no colors, no prompts)").addHelpText(
1758
+ "after",
1759
+ `
1760
+ Examples:
1761
+ $ commet pull Interactive \u2014 shows diff, asks to confirm
1762
+ $ commet pull --dry-run Preview changes without applying
1763
+ $ commet pull --yes Apply without confirmation
1764
+ $ commet pull --json --yes Agent/CI \u2014 structured JSON, no prompts
1765
+ `
1766
+ ).action(async (options) => {
1767
+ const jsonMode = options.json;
1319
1768
  if (!authExists()) {
1320
- console.log(import_chalk10.default.red("\u2717 Not authenticated"));
1321
- console.log(import_chalk10.default.dim("Run `commet login` first"));
1322
- return;
1769
+ if (jsonMode) {
1770
+ console.log(JSON.stringify({ error: "Not authenticated" }));
1771
+ } else {
1772
+ console.log(import_chalk11.default.red("\u2717 Not authenticated"));
1773
+ console.log(import_chalk11.default.dim("Run `commet login` first"));
1774
+ }
1775
+ process.exit(1);
1323
1776
  }
1324
- const hasTsConfig = validateTypeScriptProject();
1325
1777
  if (!projectConfigExists()) {
1326
- console.log(import_chalk10.default.red("\u2717 Project not linked"));
1327
- console.log(
1328
- import_chalk10.default.dim("Run `commet link` first to connect to an organization")
1329
- );
1330
- return;
1778
+ if (jsonMode) {
1779
+ console.log(JSON.stringify({ error: "Project not linked" }));
1780
+ } else {
1781
+ console.log(import_chalk11.default.red("\u2717 Project not linked"));
1782
+ console.log(
1783
+ import_chalk11.default.dim("Run `commet link` first to connect to an organization")
1784
+ );
1785
+ }
1786
+ process.exit(1);
1331
1787
  }
1332
1788
  const projectConfig = loadProjectConfig();
1333
1789
  if (!projectConfig) {
1334
- console.log(import_chalk10.default.red("\u2717 Invalid project configuration"));
1335
- return;
1790
+ if (jsonMode) {
1791
+ console.log(JSON.stringify({ error: "Invalid project configuration" }));
1792
+ } else {
1793
+ console.log(import_chalk11.default.red("\u2717 Invalid project configuration"));
1794
+ }
1795
+ process.exit(1);
1336
1796
  }
1337
- const spinner = (0, import_ora5.default)("Fetching type definitions...").start();
1797
+ const spinner = jsonMode ? null : (0, import_ora6.default)("Fetching config from remote...").start();
1338
1798
  const result = await apiRequest(
1339
- `${BASE_URL}/api/cli/types?orgId=${projectConfig.orgId}`
1799
+ `${BASE_URL}/api/cli/pull?orgId=${projectConfig.orgId}`
1340
1800
  );
1341
1801
  if (result.error || !result.data) {
1342
- spinner.fail("Failed to fetch types");
1343
- console.error(import_chalk10.default.red("Error:"), result.error);
1344
- return;
1802
+ if (jsonMode) {
1803
+ console.log(JSON.stringify({ error: result.error }));
1804
+ } else {
1805
+ spinner?.fail("Failed to fetch config");
1806
+ console.error(import_chalk11.default.red("Error:"), result.error);
1807
+ }
1808
+ process.exit(1);
1345
1809
  }
1346
- const { seatTypes, features, plans } = result.data;
1347
- const typeDefinitions = generateTypes(seatTypes, features, plans);
1348
- const commetDir = path6.resolve(process.cwd(), ".commet");
1349
- const outputPath = path6.join(commetDir, "types.d.ts");
1350
- fs6.mkdirSync(commetDir, { recursive: true });
1351
- fs6.writeFileSync(outputPath, typeDefinitions, "utf8");
1352
- spinner.succeed("Type definitions generated!");
1353
- if (hasTsConfig) {
1354
- const tsconfigResult = updateTsConfig(".commet/types.d.ts");
1355
- if (tsconfigResult.success) {
1356
- console.log(import_chalk10.default.green("\u2713 Updated tsconfig.json"));
1810
+ spinner?.succeed("Remote state fetched");
1811
+ const { features, plans } = result.data;
1812
+ const configContent = generateConfigFile(features, plans);
1813
+ const outputPath = path5.resolve(process.cwd(), "commet.config.ts");
1814
+ const existingConfigPath = findConfigFile(process.cwd());
1815
+ if (!existingConfigPath) {
1816
+ if (options.dryRun) {
1817
+ if (jsonMode) {
1818
+ console.log(
1819
+ JSON.stringify({
1820
+ action: "create",
1821
+ features: features.length,
1822
+ plans: plans.length,
1823
+ applied: false
1824
+ })
1825
+ );
1826
+ } else {
1827
+ console.log(
1828
+ import_chalk11.default.green(
1829
+ `
1830
+ Would create commet.config.ts (${features.length} features, ${plans.length} plans)`
1831
+ )
1832
+ );
1833
+ }
1834
+ return;
1835
+ }
1836
+ fs5.writeFileSync(outputPath, configContent, "utf8");
1837
+ if (jsonMode) {
1838
+ console.log(
1839
+ JSON.stringify({
1840
+ action: "create",
1841
+ features: features.length,
1842
+ plans: plans.length,
1843
+ applied: true
1844
+ })
1845
+ );
1357
1846
  } else {
1358
- console.log(import_chalk10.default.yellow("\u26A0 Could not update tsconfig.json"));
1847
+ console.log(import_chalk11.default.green(`
1848
+ \u2713 Created commet.config.ts`));
1359
1849
  console.log(
1360
- import_chalk10.default.dim(
1361
- 'Add ".commet/types.d.ts" to your tsconfig.json include array'
1362
- )
1850
+ import_chalk11.default.dim(` ${features.length} features, ${plans.length} plans`)
1363
1851
  );
1364
1852
  }
1853
+ return;
1854
+ }
1855
+ const localLoaded = await loadBillingConfig(process.cwd()).catch(
1856
+ (error) => ({
1857
+ parseError: error instanceof Error ? error.message : String(error)
1858
+ })
1859
+ );
1860
+ if ("parseError" in localLoaded) {
1861
+ if (options.dryRun) {
1862
+ if (jsonMode) {
1863
+ console.log(
1864
+ JSON.stringify({
1865
+ action: "overwrite",
1866
+ reason: localLoaded.parseError,
1867
+ applied: false
1868
+ })
1869
+ );
1870
+ } else {
1871
+ console.log(
1872
+ import_chalk11.default.yellow(
1873
+ `
1874
+ \u26A0 Local config is invalid: ${localLoaded.parseError}`
1875
+ )
1876
+ );
1877
+ }
1878
+ return;
1879
+ }
1880
+ if (!options.yes && !jsonMode) {
1881
+ console.log(import_chalk11.default.yellow(`
1882
+ \u26A0 ${localLoaded.parseError}`));
1883
+ const shouldProceed = await (0, import_prompts3.confirm)({
1884
+ message: "Overwrite with remote?",
1885
+ default: true
1886
+ });
1887
+ if (!shouldProceed) {
1888
+ console.log(import_chalk11.default.dim("Pull cancelled"));
1889
+ return;
1890
+ }
1891
+ }
1892
+ fs5.writeFileSync(outputPath, configContent, "utf8");
1893
+ if (jsonMode) {
1894
+ console.log(JSON.stringify({ action: "overwrite", applied: true }));
1895
+ } else {
1896
+ console.log(import_chalk11.default.green("\n\u2713 Overwritten commet.config.ts"));
1897
+ }
1898
+ return;
1899
+ }
1900
+ const localConfig = localLoaded.config;
1901
+ const remoteAsConfig = {
1902
+ features: Object.fromEntries(
1903
+ features.map((f) => [
1904
+ f.code,
1905
+ {
1906
+ name: f.name,
1907
+ type: f.type,
1908
+ ...f.unitName ? { unitName: f.unitName } : {},
1909
+ ...f.description ? { description: f.description } : {}
1910
+ }
1911
+ ])
1912
+ ),
1913
+ plans: Object.fromEntries(
1914
+ plans.map((p) => [
1915
+ p.code,
1916
+ {
1917
+ name: p.name,
1918
+ ...p.description ? { description: p.description } : {},
1919
+ ...p.consumptionModel ? {
1920
+ consumptionModel: p.consumptionModel
1921
+ } : {},
1922
+ ...p.isFree ? { isFree: true } : {},
1923
+ ...p.isPublic === false ? { isPublic: false } : {},
1924
+ ...p.sortOrder ? { sortOrder: p.sortOrder } : {},
1925
+ ...(() => {
1926
+ const planPrices = p.prices ?? [];
1927
+ const defaultPrice = planPrices.find((pr) => pr.isDefault);
1928
+ const defaultInterval = defaultPrice?.billingInterval ?? planPrices[0]?.billingInterval;
1929
+ return defaultInterval ? { defaultInterval } : {};
1930
+ })(),
1931
+ prices: (p.prices ?? []).map((pr) => ({
1932
+ interval: pr.billingInterval,
1933
+ amount: pr.price,
1934
+ ...pr.trialDays ? { trialDays: pr.trialDays } : {}
1935
+ }))
1936
+ }
1937
+ ])
1938
+ )
1939
+ };
1940
+ const localAsRemote = {
1941
+ features: Object.entries(localConfig.features).map(([code, f]) => ({
1942
+ code,
1943
+ name: f.name,
1944
+ type: f.type,
1945
+ description: f.description ?? null,
1946
+ unitName: f.unitName ?? null
1947
+ })),
1948
+ plans: Object.entries(localConfig.plans).map(([code, p]) => ({
1949
+ code,
1950
+ name: p.name,
1951
+ description: p.description ?? null,
1952
+ consumptionModel: p.consumptionModel ?? null,
1953
+ defaultInterval: p.defaultInterval ?? null,
1954
+ isFree: p.isFree,
1955
+ isPublic: p.isPublic,
1956
+ sortOrder: p.sortOrder,
1957
+ prices: p.prices.map((pr) => ({
1958
+ billingInterval: pr.interval,
1959
+ price: pr.amount,
1960
+ trialDays: pr.trialDays ?? null
1961
+ })),
1962
+ features: []
1963
+ }))
1964
+ };
1965
+ const diff = computeDiff(remoteAsConfig, localAsRemote);
1966
+ if (!diff.hasChanges && diff.features.unmanaged.length === 0 && diff.plans.unmanaged.length === 0) {
1967
+ if (jsonMode) {
1968
+ console.log(JSON.stringify({ diff, applied: false, upToDate: true }));
1969
+ } else {
1970
+ console.log(import_chalk11.default.green("\n\u2713 Already up to date"));
1971
+ }
1972
+ return;
1973
+ }
1974
+ if (jsonMode) {
1975
+ if (options.dryRun) {
1976
+ console.log(JSON.stringify({ diff, applied: false }));
1977
+ return;
1978
+ }
1979
+ } else {
1980
+ console.log(formatDiff(diff));
1981
+ }
1982
+ if (options.dryRun) {
1983
+ if (!jsonMode) {
1984
+ console.log(import_chalk11.default.dim("\n(dry run \u2014 no changes applied)"));
1985
+ }
1986
+ return;
1987
+ }
1988
+ if (!options.yes && !jsonMode) {
1989
+ const shouldProceed = await (0, import_prompts3.confirm)({
1990
+ message: "Overwrite commet.config.ts with remote state?",
1991
+ default: true
1992
+ });
1993
+ if (!shouldProceed) {
1994
+ console.log(import_chalk11.default.dim("Pull cancelled"));
1995
+ return;
1996
+ }
1997
+ }
1998
+ fs5.writeFileSync(outputPath, configContent, "utf8");
1999
+ if (jsonMode) {
2000
+ console.log(JSON.stringify({ diff, applied: true }));
1365
2001
  } else {
1366
- console.log(import_chalk10.default.yellow("\u26A0 No tsconfig.json found"));
2002
+ console.log(import_chalk11.default.green("\n\u2713 Updated commet.config.ts"));
1367
2003
  console.log(
1368
- import_chalk10.default.dim(
1369
- 'Add ".commet/types.d.ts" to your tsconfig.json to enable types'
1370
- )
2004
+ import_chalk11.default.dim(` ${features.length} features, ${plans.length} plans`)
1371
2005
  );
1372
2006
  }
1373
- const gitignoreResult = updateGitignore(".commet/");
1374
- if (gitignoreResult.success) {
1375
- console.log(import_chalk10.default.green("\u2713 Updated .gitignore"));
1376
- } else {
1377
- console.log(import_chalk10.default.yellow("\u26A0 No .gitignore found"));
1378
- console.log(import_chalk10.default.dim("Add .commet/ to your .gitignore file"));
2007
+ });
2008
+
2009
+ // src/commands/push.ts
2010
+ var import_prompts4 = require("@inquirer/prompts");
2011
+ var import_chalk12 = __toESM(require("chalk"));
2012
+ var import_commander10 = require("commander");
2013
+ var import_ora7 = __toESM(require("ora"));
2014
+ var pushCommand = new import_commander10.Command("push").description(
2015
+ "Push your local commet.config.ts to Commet. Creates or updates features and plans to match your config file."
2016
+ ).option("-y, --yes", "Skip confirmation prompt").option("--dry-run", "Show what would change without pushing").option("--json", "Output structured JSON (no colors, no prompts)").addHelpText(
2017
+ "after",
2018
+ `
2019
+ Examples:
2020
+ $ commet push Interactive \u2014 shows diff, asks to confirm
2021
+ $ commet push --dry-run Preview what would change on remote
2022
+ $ commet push --yes Push without confirmation
2023
+ $ commet push --json --yes Agent/CI \u2014 structured JSON, no prompts
2024
+
2025
+ Notes:
2026
+ Feature types (boolean, usage, seats) cannot be changed via push.
2027
+ Resources in remote but not in your config are left as-is (not deleted).
2028
+ `
2029
+ ).action(async (options) => {
2030
+ const jsonMode = options.json;
2031
+ if (!authExists()) {
2032
+ if (jsonMode) {
2033
+ console.log(JSON.stringify({ error: "Not authenticated" }));
2034
+ } else {
2035
+ console.log(import_chalk12.default.red("\u2717 Not authenticated"));
2036
+ console.log(import_chalk12.default.dim("Run `commet login` first"));
2037
+ }
2038
+ process.exit(1);
1379
2039
  }
1380
- console.log(import_chalk10.default.green("\nSuccess!"));
1381
- console.log(import_chalk10.default.dim("\nGenerated types:"));
1382
- console.log(
1383
- import_chalk10.default.dim(
1384
- ` Features: ${features.length > 0 ? features.map((f) => f.code).join(", ") : "none"}`
1385
- )
2040
+ if (!projectConfigExists()) {
2041
+ if (jsonMode) {
2042
+ console.log(JSON.stringify({ error: "Project not linked" }));
2043
+ } else {
2044
+ console.log(import_chalk12.default.red("\u2717 Project not linked"));
2045
+ console.log(
2046
+ import_chalk12.default.dim("Run `commet link` first to connect to an organization")
2047
+ );
2048
+ }
2049
+ process.exit(1);
2050
+ }
2051
+ const projectConfig = loadProjectConfig();
2052
+ if (!projectConfig) {
2053
+ if (jsonMode) {
2054
+ console.log(JSON.stringify({ error: "Invalid project configuration" }));
2055
+ } else {
2056
+ console.log(import_chalk12.default.red("\u2717 Invalid project configuration"));
2057
+ }
2058
+ process.exit(1);
2059
+ }
2060
+ const loadSpinner = jsonMode ? null : (0, import_ora7.default)("Loading commet.config.ts...").start();
2061
+ const loaded = await loadBillingConfig(process.cwd()).catch(
2062
+ (error) => {
2063
+ const message = error instanceof Error ? error.message : String(error);
2064
+ if (jsonMode) {
2065
+ console.log(JSON.stringify({ error: message }));
2066
+ } else {
2067
+ loadSpinner?.fail("Failed to load config");
2068
+ console.error(import_chalk12.default.red(message));
2069
+ }
2070
+ return null;
2071
+ }
1386
2072
  );
1387
- console.log(
1388
- import_chalk10.default.dim(
1389
- ` Seat types: ${seatTypes.length > 0 ? seatTypes.map((s) => s.code).join(", ") : "none"}`
1390
- )
2073
+ if (!loaded) process.exit(1);
2074
+ const { config, configPath } = loaded;
2075
+ loadSpinner?.succeed(`Loaded ${configPath}`);
2076
+ const fetchSpinner = jsonMode ? null : (0, import_ora7.default)("Fetching remote state...").start();
2077
+ const remoteResult = await apiRequest(
2078
+ `${BASE_URL}/api/cli/pull?orgId=${projectConfig.orgId}`
1391
2079
  );
1392
- console.log(
1393
- import_chalk10.default.dim(
1394
- ` Plans: ${plans.length > 0 ? plans.map((p) => p.code).join(", ") : "none"}`
1395
- )
2080
+ if (remoteResult.error || !remoteResult.data) {
2081
+ if (jsonMode) {
2082
+ console.log(JSON.stringify({ error: remoteResult.error }));
2083
+ } else {
2084
+ fetchSpinner?.fail("Failed to fetch remote state");
2085
+ console.error(import_chalk12.default.red("Error:"), remoteResult.error);
2086
+ }
2087
+ process.exit(1);
2088
+ }
2089
+ fetchSpinner?.succeed("Remote state fetched");
2090
+ const remote = {
2091
+ features: remoteResult.data.features,
2092
+ plans: remoteResult.data.plans
2093
+ };
2094
+ const diff = computeDiff(config, remote);
2095
+ if (jsonMode) {
2096
+ if (options.dryRun) {
2097
+ console.log(JSON.stringify({ diff, applied: false }));
2098
+ return;
2099
+ }
2100
+ } else {
2101
+ console.log(formatDiff(diff));
2102
+ }
2103
+ if (!diff.hasChanges) {
2104
+ if (jsonMode) {
2105
+ console.log(JSON.stringify({ diff, applied: false, upToDate: true }));
2106
+ } else {
2107
+ console.log(import_chalk12.default.green("\n\u2713 Everything is up to date"));
2108
+ }
2109
+ return;
2110
+ }
2111
+ const typeChanges = diff.features.changes.filter(
2112
+ (c) => c.action === "update" && c.changes?.some((ch) => ch.includes("BLOCKED"))
2113
+ );
2114
+ if (typeChanges.length > 0) {
2115
+ const blockedCodes = typeChanges.map((c) => c.code);
2116
+ if (jsonMode) {
2117
+ console.log(
2118
+ JSON.stringify({
2119
+ error: "Feature type changes blocked",
2120
+ blockedCodes,
2121
+ diff
2122
+ })
2123
+ );
2124
+ } else {
2125
+ console.log(
2126
+ import_chalk12.default.red(
2127
+ "\n\u2717 Cannot change feature types. Update them in the dashboard:"
2128
+ )
2129
+ );
2130
+ for (const change of typeChanges) {
2131
+ console.log(import_chalk12.default.red(` - ${change.code}`));
2132
+ }
2133
+ }
2134
+ process.exit(1);
2135
+ }
2136
+ if (options.dryRun) {
2137
+ if (!jsonMode) {
2138
+ console.log(import_chalk12.default.dim("\n(dry run \u2014 no changes applied)"));
2139
+ }
2140
+ return;
2141
+ }
2142
+ if (!options.yes && !jsonMode) {
2143
+ const shouldProceed = await (0, import_prompts4.confirm)({
2144
+ message: "Apply these changes?",
2145
+ default: true
2146
+ });
2147
+ if (!shouldProceed) {
2148
+ console.log(import_chalk12.default.dim("Push cancelled"));
2149
+ return;
2150
+ }
2151
+ }
2152
+ const pushSpinner = jsonMode ? null : (0, import_ora7.default)("Pushing config...").start();
2153
+ const pushResult = await apiRequest(
2154
+ `${BASE_URL}/api/cli/push`,
2155
+ {
2156
+ method: "POST",
2157
+ body: JSON.stringify({
2158
+ orgId: projectConfig.orgId,
2159
+ config: {
2160
+ features: config.features,
2161
+ plans: config.plans
2162
+ }
2163
+ })
2164
+ }
1396
2165
  );
1397
- console.log(import_chalk10.default.dim(`
1398
- Output: ${outputPath}`));
1399
- if (seatTypes.length === 0 && plans.length === 0 && features.length === 0) {
2166
+ if (pushResult.error || !pushResult.data) {
2167
+ if (jsonMode) {
2168
+ console.log(JSON.stringify({ error: pushResult.error }));
2169
+ } else {
2170
+ pushSpinner?.fail("Push failed");
2171
+ console.error(import_chalk12.default.red("Error:"), pushResult.error);
2172
+ }
2173
+ process.exit(1);
2174
+ }
2175
+ const pushOutcome = pushResult.data;
2176
+ if (jsonMode) {
2177
+ console.log(JSON.stringify({ diff, applied: true, result: pushOutcome }));
2178
+ return;
2179
+ }
2180
+ const errors = [
2181
+ ...pushOutcome.features.errors,
2182
+ ...pushOutcome.plans.errors
2183
+ ];
2184
+ if (errors.length > 0) {
2185
+ pushSpinner?.warn("Push completed with errors");
2186
+ for (const error of errors) {
2187
+ console.log(import_chalk12.default.red(` \u2717 ${error.code}: ${error.message}`));
2188
+ }
2189
+ } else {
2190
+ pushSpinner?.succeed("Push complete");
2191
+ }
2192
+ if (pushOutcome.features.created.length > 0) {
2193
+ console.log(
2194
+ import_chalk12.default.green(
2195
+ ` Created features: ${pushOutcome.features.created.join(", ")}`
2196
+ )
2197
+ );
2198
+ }
2199
+ if (pushOutcome.features.updated.length > 0) {
2200
+ console.log(
2201
+ import_chalk12.default.yellow(
2202
+ ` Updated features: ${pushOutcome.features.updated.join(", ")}`
2203
+ )
2204
+ );
2205
+ }
2206
+ if (pushOutcome.plans.created.length > 0) {
2207
+ console.log(
2208
+ import_chalk12.default.green(` Created plans: ${pushOutcome.plans.created.join(", ")}`)
2209
+ );
2210
+ }
2211
+ if (pushOutcome.plans.updated.length > 0) {
1400
2212
  console.log(
1401
- import_chalk10.default.yellow(
1402
- "\n\u26A0 No types found. Create features, seat types, and plans in your Commet dashboard."
2213
+ import_chalk12.default.yellow(
2214
+ ` Updated plans: ${pushOutcome.plans.updated.join(", ")}`
1403
2215
  )
1404
2216
  );
1405
2217
  }
1406
2218
  });
1407
2219
 
1408
2220
  // src/commands/switch.ts
1409
- var import_prompts3 = require("@inquirer/prompts");
1410
- var import_chalk11 = __toESM(require("chalk"));
1411
- var import_commander9 = require("commander");
1412
- var import_ora6 = __toESM(require("ora"));
1413
- var switchCommand = new import_commander9.Command("switch").description("Switch to a different organization").action(async () => {
2221
+ var import_prompts5 = require("@inquirer/prompts");
2222
+ var import_chalk13 = __toESM(require("chalk"));
2223
+ var import_commander11 = require("commander");
2224
+ var import_ora8 = __toESM(require("ora"));
2225
+ var switchCommand = new import_commander11.Command("switch").description(
2226
+ "Switch this project to a different organization. Updates .commet/config.json with the new org."
2227
+ ).option(
2228
+ "--org <slug-or-id>",
2229
+ "Organization slug or ID \u2014 skips interactive selection"
2230
+ ).addHelpText(
2231
+ "after",
2232
+ `
2233
+ Examples:
2234
+ $ commet switch Interactive \u2014 choose from a list
2235
+ $ commet switch --org acme Non-interactive \u2014 match by slug or ID
2236
+
2237
+ Use 'commet orgs' to see available organizations and their slugs.
2238
+ After switching, run 'commet pull' to update your config file.
2239
+ `
2240
+ ).action(async (options) => {
1414
2241
  if (!authExists()) {
1415
- console.log(import_chalk11.default.red("\u2717 Not authenticated"));
1416
- console.log(import_chalk11.default.dim("Run `commet login` first"));
1417
- return;
2242
+ console.log(import_chalk13.default.red("\u2717 Not authenticated"));
2243
+ console.log(import_chalk13.default.dim("Run `commet login` first"));
2244
+ process.exit(1);
1418
2245
  }
1419
2246
  if (!projectConfigExists()) {
1420
- console.log(import_chalk11.default.yellow("\u26A0 Project not linked"));
2247
+ console.log(import_chalk13.default.yellow("\u26A0 Project not linked"));
1421
2248
  console.log(
1422
- import_chalk11.default.dim("Run `commet link` first to connect to an organization")
2249
+ import_chalk13.default.dim("Run `commet link` first to connect to an organization")
1423
2250
  );
1424
- return;
2251
+ process.exit(1);
1425
2252
  }
1426
- const spinner = (0, import_ora6.default)("Fetching organizations...").start();
2253
+ const spinner = (0, import_ora8.default)("Fetching organizations...").start();
1427
2254
  const result = await apiRequest(
1428
2255
  `${BASE_URL}/api/cli/organizations`
1429
2256
  );
1430
2257
  if (result.error || !result.data) {
1431
2258
  spinner.fail("Failed to fetch organizations");
1432
- console.error(import_chalk11.default.red("Error:"), result.error);
1433
- return;
2259
+ console.error(import_chalk13.default.red("Error:"), result.error);
2260
+ process.exit(1);
1434
2261
  }
1435
2262
  const { organizations } = result.data;
1436
2263
  if (organizations.length === 0) {
1437
2264
  spinner.stop();
1438
- console.log(import_chalk11.default.yellow("\u26A0 No organizations found"));
1439
- return;
2265
+ console.log(import_chalk13.default.yellow("\u26A0 No organizations found"));
2266
+ process.exit(1);
1440
2267
  }
1441
2268
  spinner.stop();
1442
- let orgId;
1443
- try {
1444
- orgId = await (0, import_prompts3.select)({
1445
- message: "Select organization:",
1446
- choices: organizations.map((org) => ({
1447
- name: `${org.name} ${import_chalk11.default.dim(`(${org.slug}) \xB7 ${org.mode}`)}`,
1448
- value: org.id
1449
- })),
1450
- theme: promptTheme
1451
- });
1452
- } catch (_error) {
1453
- console.log(import_chalk11.default.yellow("\n\u26A0 Switch cancelled"));
1454
- return;
1455
- }
1456
- const selectedOrg = organizations.find((org) => org.id === orgId);
1457
- if (!selectedOrg) {
1458
- console.log(import_chalk11.default.red("\u2717 Organization not found"));
1459
- return;
2269
+ let selectedOrg;
2270
+ if (options.org) {
2271
+ selectedOrg = organizations.find(
2272
+ (org) => org.slug === options.org || org.id === options.org
2273
+ );
2274
+ if (!selectedOrg) {
2275
+ console.log(import_chalk13.default.red(`\u2717 Organization "${options.org}" not found`));
2276
+ console.log(import_chalk13.default.dim("\nAvailable organizations:"));
2277
+ for (const org of organizations) {
2278
+ console.log(import_chalk13.default.dim(` ${org.slug} (${org.mode})`));
2279
+ }
2280
+ process.exit(1);
2281
+ }
2282
+ } else {
2283
+ let orgId;
2284
+ try {
2285
+ orgId = await (0, import_prompts5.select)({
2286
+ message: "Select organization:",
2287
+ choices: organizations.map((org) => ({
2288
+ name: `${org.name} ${import_chalk13.default.dim(`(${org.slug}) \xB7 ${org.mode}`)}`,
2289
+ value: org.id
2290
+ })),
2291
+ theme: promptTheme
2292
+ });
2293
+ } catch (_error) {
2294
+ console.log(import_chalk13.default.yellow("\n\u26A0 Switch cancelled"));
2295
+ return;
2296
+ }
2297
+ selectedOrg = organizations.find((org) => org.id === orgId);
2298
+ if (!selectedOrg) {
2299
+ console.log(import_chalk13.default.red("\u2717 Organization not found"));
2300
+ process.exit(1);
2301
+ }
1460
2302
  }
1461
2303
  saveProjectConfig({
1462
2304
  orgId: selectedOrg.id,
1463
2305
  orgName: selectedOrg.name,
1464
2306
  mode: selectedOrg.mode
1465
2307
  });
1466
- console.log(import_chalk11.default.green("\n\u2713 Switched organization successfully!"));
2308
+ console.log(import_chalk13.default.green("\n\u2713 Switched organization"));
1467
2309
  console.log(
1468
- import_chalk11.default.dim(`
1469
- Organization: ${selectedOrg.name} \xB7 ${selectedOrg.mode}`)
2310
+ import_chalk13.default.dim(`Organization: ${selectedOrg.name} \xB7 ${selectedOrg.mode}`)
1470
2311
  );
1471
2312
  console.log(
1472
- import_chalk11.default.dim(
2313
+ import_chalk13.default.dim(
1473
2314
  "\nRun `commet pull` to update TypeScript types for this organization"
1474
2315
  )
1475
2316
  );
1476
2317
  });
1477
2318
 
1478
2319
  // src/commands/unlink.ts
1479
- var import_chalk12 = __toESM(require("chalk"));
1480
- var import_commander10 = require("commander");
1481
- var unlinkCommand = new import_commander10.Command("unlink").description("Unlink this project from Commet").action(async () => {
2320
+ var import_chalk14 = __toESM(require("chalk"));
2321
+ var import_commander12 = require("commander");
2322
+ var unlinkCommand = new import_commander12.Command("unlink").description(
2323
+ "Unlink this project from its organization. Removes the .commet/ directory. Does not delete anything on remote."
2324
+ ).action(async () => {
1482
2325
  if (!projectConfigExists()) {
1483
2326
  console.log(
1484
- import_chalk12.default.yellow("\u26A0 This project is not linked to any organization")
2327
+ import_chalk14.default.yellow("\u26A0 This project is not linked to any organization")
1485
2328
  );
1486
2329
  return;
1487
2330
  }
1488
2331
  clearProjectConfig();
1489
- console.log(import_chalk12.default.green("\u2713 Project unlinked successfully"));
1490
- console.log(import_chalk12.default.dim("\u2713 Removed .commet/ directory"));
2332
+ console.log(import_chalk14.default.green("\u2713 Project unlinked successfully"));
2333
+ console.log(import_chalk14.default.dim("\u2713 Removed .commet/ directory"));
1491
2334
  console.log(
1492
- import_chalk12.default.dim("\nRun `commet link` to connect to a different organization")
2335
+ import_chalk14.default.dim("\nRun `commet link` to connect to a different organization")
1493
2336
  );
1494
2337
  });
1495
2338
 
1496
- // src/commands/whoami.ts
1497
- var import_chalk13 = __toESM(require("chalk"));
1498
- var import_commander11 = require("commander");
1499
- var whoamiCommand = new import_commander11.Command("whoami").description("Display current authentication and project status").action(async () => {
1500
- if (!authExists()) {
1501
- console.log(import_chalk13.default.yellow("\u26A0 Not logged in"));
1502
- console.log(import_chalk13.default.dim("Run `commet login` to authenticate"));
1503
- return;
1504
- }
1505
- console.log(import_chalk13.default.green("\u2713 Logged in"));
1506
- const projectConfig = loadProjectConfig();
1507
- if (projectConfig) {
1508
- console.log(import_chalk13.default.bold("\nProject:"));
1509
- console.log(import_chalk13.default.dim("Organization:"), projectConfig.orgName);
1510
- console.log(import_chalk13.default.dim("Mode:"), projectConfig.mode);
1511
- } else {
1512
- console.log(import_chalk13.default.yellow("\n\u26A0 No project linked"));
1513
- console.log(
1514
- import_chalk13.default.dim(
1515
- "Run `commet link` to connect this directory to an organization"
1516
- )
1517
- );
1518
- }
1519
- });
1520
-
1521
2339
  // src/index.ts
1522
- var program = new import_commander12.Command();
2340
+ var program = new import_commander13.Command();
1523
2341
  program.name("commet").description(
1524
- "Commet CLI - Manage your billing platform from the command line"
1525
- ).version(package_default.version);
2342
+ "Commet CLI \u2014 billing infrastructure as code.\nManage features, plans, and billing config from the command line."
2343
+ ).version(package_default.version).addHelpText(
2344
+ "after",
2345
+ `
2346
+ Workflow:
2347
+ $ commet pull Sync remote \u2192 commet.config.ts
2348
+ $ commet push Push local changes \u2192 remote
2349
+ $ commet list features See what's configured
2350
+
2351
+ For agents and CI:
2352
+ $ commet agent-info JSON with status + every command's usage
2353
+ $ commet pull --json --yes Structured output, no prompts
2354
+ $ commet orgs --json List orgs as JSON
2355
+ $ commet link --org <slug> Link without interactive selection
2356
+
2357
+ Run commet <command> --help for detailed usage and examples.
2358
+ `
2359
+ );
1526
2360
  program.addCommand(createCommand);
1527
2361
  program.addCommand(loginCommand);
1528
2362
  program.addCommand(logoutCommand);
1529
- program.addCommand(whoamiCommand);
1530
2363
  program.addCommand(linkCommand);
1531
2364
  program.addCommand(unlinkCommand);
1532
2365
  program.addCommand(switchCommand);
1533
- program.addCommand(infoCommand);
2366
+ program.addCommand(agentInfoCommand);
2367
+ program.addCommand(orgsCommand);
2368
+ program.addCommand(pushCommand);
1534
2369
  program.addCommand(pullCommand);
1535
2370
  program.addCommand(listCommand);
1536
2371
  program.addCommand(listenCommand);
2372
+ program.showSuggestionAfterError();
1537
2373
  program.exitOverride();
1538
2374
  try {
1539
2375
  program.parse(process.argv);
@@ -1543,10 +2379,69 @@ try {
1543
2379
  if (code === "commander.version" || code === "commander.help" || code === "commander.helpDisplayed") {
1544
2380
  process.exit(0);
1545
2381
  }
1546
- console.error(import_chalk14.default.red("Error:"), error.message);
2382
+ console.error(import_chalk15.default.red("Error:"), error.message);
1547
2383
  }
1548
2384
  process.exit(1);
1549
2385
  }
1550
2386
  if (!process.argv.slice(2).length) {
1551
- program.outputHelp();
2387
+ printDefaultScreen();
2388
+ }
2389
+ function printDefaultScreen() {
2390
+ const version = import_chalk15.default.dim(`v${package_default.version}`);
2391
+ console.log(`
2392
+ ${commetColor.bold("Commet")} ${version}`);
2393
+ console.log(import_chalk15.default.dim(" Billing infrastructure as code\n"));
2394
+ const authenticated = authExists();
2395
+ const projectConfig = projectConfigExists() ? loadProjectConfig() : null;
2396
+ const configFile = findConfigFile(process.cwd());
2397
+ if (authenticated) {
2398
+ console.log(import_chalk15.default.green(" \u2713 Authenticated"));
2399
+ } else {
2400
+ console.log(
2401
+ ` ${import_chalk15.default.yellow("\u26A0")} Not logged in ${import_chalk15.default.dim("\u2192 commet login")}`
2402
+ );
2403
+ }
2404
+ if (projectConfig) {
2405
+ console.log(
2406
+ import_chalk15.default.green(
2407
+ ` \u2713 ${projectConfig.orgName} ${import_chalk15.default.dim(`(${projectConfig.mode})`)}`
2408
+ )
2409
+ );
2410
+ console.log(import_chalk15.default.dim(` ${projectConfig.orgId}`));
2411
+ } else if (authenticated) {
2412
+ console.log(
2413
+ ` ${import_chalk15.default.yellow("\u26A0")} No project linked ${import_chalk15.default.dim("\u2192 commet link")}`
2414
+ );
2415
+ }
2416
+ if (configFile) {
2417
+ const fileName = configFile.split("/").pop();
2418
+ console.log(import_chalk15.default.green(` \u2713 ${fileName}`));
2419
+ } else if (projectConfig) {
2420
+ console.log(
2421
+ ` ${import_chalk15.default.yellow("\u26A0")} No config file ${import_chalk15.default.dim("\u2192 commet pull")}`
2422
+ );
2423
+ }
2424
+ const cmd = (name) => commetColor(name.padEnd(22));
2425
+ const dim = import_chalk15.default.dim;
2426
+ console.log(dim("\n Config"));
2427
+ console.log(` ${cmd("pull")}${dim("Sync remote \u2192 commet.config.ts")}`);
2428
+ console.log(` ${cmd("push")}${dim("Sync commet.config.ts \u2192 remote")}`);
2429
+ console.log(
2430
+ ` ${cmd("list <type>")}${dim("List features, plans, or seats")}`
2431
+ );
2432
+ console.log(dim("\n Development"));
2433
+ console.log(` ${cmd("listen <url>")}${dim("Forward webhooks locally")}`);
2434
+ console.log(dim("\n Project"));
2435
+ console.log(` ${cmd("link")}${dim("Link to an organization")}`);
2436
+ console.log(` ${cmd("switch")}${dim("Switch organization")}`);
2437
+ console.log(` ${cmd("orgs")}${dim("List organizations")}`);
2438
+ console.log(dim("\n Setup"));
2439
+ console.log(` ${cmd("create")}${dim("Scaffold a new Commet app")}`);
2440
+ console.log(` ${cmd("login")}${dim("Authenticate")}`);
2441
+ console.log(` ${cmd("logout")}${dim("Log out")}`);
2442
+ console.log(
2443
+ dim(
2444
+ "\n commet --help for full reference \xB7 commet agent-info for agents/CI\n"
2445
+ )
2446
+ );
1552
2447
  }