commet 1.11.0 → 2.0.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 +1027 -959
  2. package/package.json +1 -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_chalk15 = __toESM(require("chalk"));
28
- var import_commander13 = require("commander");
27
+ var import_chalk14 = __toESM(require("chalk"));
28
+ var import_commander10 = require("commander");
29
29
 
30
30
  // package.json
31
31
  var package_default = {
32
32
  name: "commet",
33
- version: "1.11.0",
33
+ version: "2.0.0",
34
34
  description: "Commet CLI - Manage your billing platform from the command line",
35
35
  bin: {
36
36
  commet: "./bin/commet"
@@ -87,8 +87,10 @@ var package_default = {
87
87
  }
88
88
  };
89
89
 
90
- // src/commands/agent-info.ts
90
+ // src/commands/api-key.ts
91
+ var import_chalk2 = __toESM(require("chalk"));
91
92
  var import_commander = require("commander");
93
+ var import_ora = __toESM(require("ora"));
92
94
 
93
95
  // src/utils/config.ts
94
96
  var fs = __toESM(require("fs"));
@@ -157,260 +159,353 @@ function clearProjectConfig() {
157
159
  }
158
160
  }
159
161
 
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;
162
+ // src/utils/telemetry.ts
163
+ var CLI_VERSION = true ? "2.0.0" : "0.0.0";
164
+ var TELEMETRY_URL = "https://commet.co/api/cli/telemetry";
165
+ function detectRuntime() {
166
+ if ("Bun" in globalThis) {
167
+ const bun = globalThis.Bun;
168
+ if (bun && typeof bun.version === "string")
169
+ return { name: "bun", version: bun.version };
170
+ }
171
+ if ("Deno" in globalThis) {
172
+ const deno = globalThis.Deno;
173
+ if (deno?.version?.deno)
174
+ return { name: "deno", version: deno.version.deno };
175
+ }
176
+ return { name: "node", version: process.versions.node };
177
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 };
178
+ var cachedClientInfo = null;
179
+ var cachedUserAgent = null;
180
+ function collectClientInfo() {
181
+ const runtime = detectRuntime();
182
+ return {
183
+ sdk: "commet-cli",
184
+ sdkVersion: CLI_VERSION,
185
+ lang: "node",
186
+ langVersion: process.versions.node,
187
+ platform: process.platform,
188
+ arch: process.arch,
189
+ runtime: runtime.name,
190
+ runtimeVersion: runtime.version
191
+ };
199
192
  }
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
- }
193
+ function getClientInfoHeader() {
194
+ if (!cachedClientInfo) {
195
+ cachedClientInfo = JSON.stringify(collectClientInfo());
267
196
  }
197
+ return cachedClientInfo;
268
198
  }
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
- );
199
+ function getUserAgent() {
200
+ if (!cachedUserAgent) {
201
+ const runtime = detectRuntime();
202
+ cachedUserAgent = `commet-cli/${CLI_VERSION} ${runtime.name}/${runtime.version} ${process.platform}/${process.arch}`;
282
203
  }
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
- );
204
+ return cachedUserAgent;
205
+ }
206
+ function isTelemetryDisabled() {
207
+ return process.env.COMMET_TELEMETRY_DISABLED === "1" || process.env.DO_NOT_TRACK === "1";
208
+ }
209
+ function resolveOrgId() {
210
+ if (process.env.COMMET_API_KEY) return null;
211
+ try {
212
+ const config = loadProjectConfig();
213
+ return config?.orgId ?? null;
214
+ } catch {
215
+ return null;
287
216
  }
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
217
+ }
218
+ var commandStartTime = null;
219
+ var apiRequestMade = false;
220
+ var commandReported = false;
221
+ function markCommandStart() {
222
+ commandStartTime = Date.now();
223
+ apiRequestMade = false;
224
+ commandReported = false;
225
+ }
226
+ function markApiRequest() {
227
+ apiRequestMade = true;
228
+ }
229
+ function sendTelemetry(payload) {
230
+ const controller = new AbortController();
231
+ const timeout = setTimeout(() => controller.abort(), 750);
232
+ fetch(TELEMETRY_URL, {
233
+ method: "POST",
234
+ headers: {
235
+ "Content-Type": "application/json",
236
+ "User-Agent": getUserAgent()
301
237
  },
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");
238
+ body: JSON.stringify(payload),
239
+ signal: controller.signal
240
+ }).catch(() => {
241
+ }).finally(() => clearTimeout(timeout));
242
+ }
243
+ function reportCommand(command, outcome, errorCode) {
244
+ if (commandReported) return;
245
+ if (isTelemetryDisabled()) return;
246
+ if (apiRequestMade && outcome === "success") return;
247
+ commandReported = true;
248
+ const durationMs = commandStartTime ? Date.now() - commandStartTime : null;
249
+ const runtime = detectRuntime();
250
+ const orgId = resolveOrgId();
251
+ sendTelemetry({
252
+ type: "command",
253
+ command,
254
+ outcome,
255
+ ...errorCode ? { errorCode } : {},
256
+ ...orgId ? { orgId } : {},
257
+ cliVersion: CLI_VERSION,
258
+ runtime: runtime.name,
259
+ runtimeVersion: runtime.version,
260
+ platform: process.platform,
261
+ arch: process.arch,
262
+ ...durationMs !== null ? { durationMs } : {},
263
+ authMode: process.env.COMMET_API_KEY ? "api_key" : "login"
264
+ });
265
+ }
266
+ function reportCrash(error) {
267
+ if (commandReported) return;
268
+ if (isTelemetryDisabled()) return;
269
+ commandReported = true;
270
+ const command = process.argv[2] || "(default)";
271
+ const orgId = resolveOrgId();
272
+ const errorName = error instanceof Error ? error.constructor.name : "UnknownError";
273
+ sendTelemetry({
274
+ type: "crash",
275
+ command,
276
+ errorName,
277
+ ...orgId ? { orgId } : {},
278
+ cliVersion: CLI_VERSION,
279
+ platform: process.platform,
280
+ arch: process.arch,
281
+ authMode: process.env.COMMET_API_KEY ? "api_key" : "login"
282
+ });
283
+ }
284
+ function installCrashHandler() {
285
+ process.on("uncaughtException", (error) => {
286
+ reportCrash(error);
287
+ console.error("Fatal:", error.message);
288
+ setTimeout(() => process.exit(1), 800);
289
+ });
290
+ process.on("unhandledRejection", (reason) => {
291
+ reportCrash(reason);
292
+ console.error(
293
+ "Fatal:",
294
+ reason instanceof Error ? reason.message : String(reason)
295
+ );
296
+ setTimeout(() => process.exit(1), 800);
297
+ });
298
+ }
364
299
 
365
300
  // src/utils/api.ts
366
301
  var BASE_URL = "https://commet.co";
367
302
  async function apiRequest(endpoint, options = {}) {
368
- const auth = loadAuth();
369
- if (!auth) {
370
- return { error: "Not authenticated. Run `commet login` first." };
303
+ const apiKey = process.env.COMMET_API_KEY;
304
+ const auth = apiKey ? null : loadAuth();
305
+ if (!apiKey && !auth) {
306
+ return {
307
+ error: {
308
+ code: "auth_required",
309
+ message: "Not authenticated. Run `commet login` first."
310
+ }
311
+ };
371
312
  }
372
313
  try {
314
+ markApiRequest();
373
315
  const response = await fetch(endpoint, {
374
316
  ...options,
375
317
  headers: {
376
318
  ...options.headers,
377
- Authorization: `Bearer ${auth.token}`,
378
- "Content-Type": "application/json"
319
+ "Content-Type": "application/json",
320
+ "User-Agent": getUserAgent(),
321
+ "commet-client-info": getClientInfoHeader(),
322
+ ...apiKey ? { "x-api-key": apiKey } : { Authorization: `Bearer ${auth.token}` }
379
323
  }
380
324
  });
381
325
  if (!response.ok) {
382
326
  const errorData = await response.json().catch(() => ({}));
383
327
  return {
384
- error: errorData.message || errorData.error || `Request failed with status ${response.status}`
328
+ error: {
329
+ code: errorData.code ?? `http_${response.status}`,
330
+ message: errorData.message ?? errorData.error ?? `Request failed with status ${response.status}`
331
+ }
385
332
  };
386
333
  }
387
334
  const data = await response.json();
388
335
  return { data };
389
336
  } catch (error) {
390
337
  return {
391
- error: error instanceof Error ? error.message : "Unknown error occurred"
338
+ error: {
339
+ code: "network_error",
340
+ message: error instanceof Error ? error.message : "Unknown error occurred"
341
+ }
392
342
  };
393
343
  }
394
344
  }
395
345
 
346
+ // src/utils/output.ts
347
+ var import_chalk = __toESM(require("chalk"));
348
+ function isAgentMode(options) {
349
+ if (options?.output === "agent") return true;
350
+ const idx = process.argv.indexOf("--output");
351
+ return idx !== -1 && process.argv[idx + 1] === "agent";
352
+ }
353
+ function exitWithError(error) {
354
+ const command = process.argv[2] || "(default)";
355
+ reportCommand(command, "error", error.code);
356
+ if (isAgentMode()) {
357
+ console.log(JSON.stringify({ error }));
358
+ } else {
359
+ console.log(import_chalk.default.red(`\u2717 ${error.message}`));
360
+ if (error.action) {
361
+ console.log(import_chalk.default.dim(`Run \`${error.action}\``));
362
+ }
363
+ }
364
+ process.exit(1);
365
+ }
366
+ function requireAuth() {
367
+ if (process.env.COMMET_API_KEY) return;
368
+ if (!authExists()) {
369
+ exitWithError({
370
+ code: "auth_required",
371
+ message: "Not authenticated",
372
+ action: "commet login"
373
+ });
374
+ }
375
+ }
376
+ function requireOrgContext() {
377
+ if (process.env.COMMET_API_KEY) {
378
+ return { orgId: "__from_api_key__" };
379
+ }
380
+ requireAuth();
381
+ const projectConfig = loadProjectConfig();
382
+ if (!projectConfig) {
383
+ exitWithError({
384
+ code: "project_not_linked",
385
+ message: "No organization linked",
386
+ action: "commet link"
387
+ });
388
+ }
389
+ return { orgId: projectConfig.orgId };
390
+ }
391
+
392
+ // src/commands/api-key.ts
393
+ var apiKeyCommand = new import_commander.Command("api-key").description(
394
+ "Generate an API key for the linked organization. Use it in CI with COMMET_API_KEY env var."
395
+ ).option("--name <name>", "Name for the API key", "CLI").option(
396
+ "--output <format>",
397
+ "Output format: human (default) or agent",
398
+ "human"
399
+ ).addHelpText(
400
+ "after",
401
+ `
402
+ Examples:
403
+ $ commet api-key Generate a key for the linked org
404
+ $ commet api-key --name "GitHub CI" Name it for easy identification
405
+ $ commet api-key --output agent JSON output with the key
406
+
407
+ Then use it in CI:
408
+ $ COMMET_API_KEY=sk_... commet push --yes
409
+ `
410
+ ).action(async (options) => {
411
+ const agentMode = isAgentMode(options);
412
+ if (process.env.COMMET_API_KEY) {
413
+ exitWithError({
414
+ code: "invalid_context",
415
+ message: "Cannot create API keys while using COMMET_API_KEY",
416
+ action: "commet login"
417
+ });
418
+ }
419
+ requireAuth();
420
+ const projectConfig = loadProjectConfig();
421
+ if (!projectConfig) {
422
+ exitWithError({
423
+ code: "project_not_linked",
424
+ message: "No organization linked",
425
+ action: "commet link"
426
+ });
427
+ }
428
+ const spinner = agentMode ? null : (0, import_ora.default)("Generating API key...").start();
429
+ const result = await apiRequest(
430
+ `${BASE_URL}/api/cli/api-keys`,
431
+ {
432
+ method: "POST",
433
+ body: JSON.stringify({
434
+ organizationId: projectConfig.orgId,
435
+ name: options.name
436
+ })
437
+ }
438
+ );
439
+ if (result.error || !result.data) {
440
+ if (agentMode) {
441
+ console.log(JSON.stringify({ error: result.error }));
442
+ } else {
443
+ spinner?.fail("Failed to create API key");
444
+ console.error(import_chalk2.default.red("Error:"), result.error?.message);
445
+ }
446
+ process.exit(1);
447
+ }
448
+ const { apiKey } = result.data;
449
+ const isLive = projectConfig.mode === "live";
450
+ if (agentMode) {
451
+ console.log(
452
+ JSON.stringify({
453
+ success: true,
454
+ apiKey,
455
+ mode: projectConfig.mode,
456
+ ...isLive ? {
457
+ warning: "This is a live API key. Rotate it before using in production if generated by an agent."
458
+ } : {}
459
+ })
460
+ );
461
+ return;
462
+ }
463
+ spinner?.succeed("API key created");
464
+ console.log("");
465
+ console.log(` ${import_chalk2.default.bold(apiKey)}`);
466
+ console.log("");
467
+ console.log(import_chalk2.default.yellow(" \u26A0 This key is shown only once \u2014 copy it now."));
468
+ if (isLive) {
469
+ console.log(
470
+ import_chalk2.default.yellow(
471
+ " \u26A0 This is a live key. Rotate it from the dashboard before production use."
472
+ )
473
+ );
474
+ }
475
+ console.log(
476
+ import_chalk2.default.dim("\n Use in CI: COMMET_API_KEY=<key> commet push --yes")
477
+ );
478
+ });
479
+
480
+ // src/commands/create.ts
481
+ var import_node_child_process = require("child_process");
482
+ var fs2 = __toESM(require("fs"));
483
+ var os2 = __toESM(require("os"));
484
+ var path2 = __toESM(require("path"));
485
+ var import_prompts = require("@inquirer/prompts");
486
+ var import_chalk5 = __toESM(require("chalk"));
487
+ var import_commander2 = require("commander");
488
+ var import_ora3 = __toESM(require("ora"));
489
+ var import_tar = require("tar");
490
+
396
491
  // src/utils/login-flow.ts
397
- var import_chalk2 = __toESM(require("chalk"));
492
+ var import_chalk4 = __toESM(require("chalk"));
398
493
  var import_open = __toESM(require("open"));
399
- var import_ora = __toESM(require("ora"));
494
+ var import_ora2 = __toESM(require("ora"));
400
495
 
401
496
  // src/utils/prompt-theme.ts
402
- var import_chalk = __toESM(require("chalk"));
403
- var commetColor = import_chalk.default.hex("#e8a07c");
497
+ var import_chalk3 = __toESM(require("chalk"));
498
+ var commetColor = import_chalk3.default.hex("#e8a07c");
404
499
  var promptTheme = {
405
500
  prefix: commetColor("\u276F"),
406
501
  style: {
407
502
  answer: commetColor,
408
- message: import_chalk.default.bold,
409
- error: import_chalk.default.red,
410
- help: import_chalk.default.dim,
503
+ message: import_chalk3.default.bold,
504
+ error: import_chalk3.default.red,
505
+ help: import_chalk3.default.dim,
411
506
  highlight: commetColor.bold,
412
- description: import_chalk.default.dim,
413
- defaultAnswer: import_chalk.default.dim
507
+ description: import_chalk3.default.dim,
508
+ defaultAnswer: import_chalk3.default.dim
414
509
  }
415
510
  };
416
511
 
@@ -419,7 +514,7 @@ function sleep(ms) {
419
514
  return new Promise((resolve4) => setTimeout(resolve4, ms));
420
515
  }
421
516
  async function performLogin() {
422
- const spinner = (0, import_ora.default)("Initiating login flow...").start();
517
+ const spinner = (0, import_ora2.default)("Initiating login flow...").start();
423
518
  try {
424
519
  const deviceResponse = await fetch(`${BASE_URL}/api/auth/device/code`, {
425
520
  method: "POST",
@@ -441,18 +536,18 @@ async function performLogin() {
441
536
  interval = 5
442
537
  } = deviceData;
443
538
  spinner.stop();
444
- console.log(import_chalk2.default.bold("\n\u{1F510} Commet CLI Login\n"));
539
+ console.log(import_chalk4.default.bold("\n\u{1F510} Commet CLI Login\n"));
445
540
  console.log("Visit the following URL in your browser:");
446
541
  console.log(commetColor.underline(verification_uri_complete));
447
542
  console.log("\nOr enter this code manually:");
448
- console.log(import_chalk2.default.bold.green(` ${user_code}`));
449
- console.log(import_chalk2.default.dim("\nOpening browser...\n"));
543
+ console.log(import_chalk4.default.bold.green(` ${user_code}`));
544
+ console.log(import_chalk4.default.dim("\nOpening browser...\n"));
450
545
  try {
451
546
  await (0, import_open.default)(verification_uri_complete);
452
547
  } catch {
453
- console.log(import_chalk2.default.yellow("\u26A0 Could not open browser automatically."));
548
+ console.log(import_chalk4.default.yellow("\u26A0 Could not open browser automatically."));
454
549
  }
455
- const pollSpinner = (0, import_ora.default)("Waiting for authorization...").start();
550
+ const pollSpinner = (0, import_ora2.default)("Waiting for authorization...").start();
456
551
  let pollingInterval = interval;
457
552
  let attempts = 0;
458
553
  const maxAttempts = Math.floor(180 / pollingInterval);
@@ -560,11 +655,11 @@ async function downloadTemplate(templateDir, dest, ref) {
560
655
  if (!response.ok) {
561
656
  throw new Error(`Failed to download (HTTP ${response.status})`);
562
657
  }
563
- const tempFile = path3.join(os2.tmpdir(), `commet-${Date.now()}.tar.gz`);
658
+ const tempFile = path2.join(os2.tmpdir(), `commet-${Date.now()}.tar.gz`);
564
659
  try {
565
660
  const buffer = Buffer.from(await response.arrayBuffer());
566
- fs3.writeFileSync(tempFile, buffer);
567
- fs3.mkdirSync(dest, { recursive: true });
661
+ fs2.writeFileSync(tempFile, buffer);
662
+ fs2.mkdirSync(dest, { recursive: true });
568
663
  await (0, import_tar.extract)({
569
664
  file: tempFile,
570
665
  cwd: dest,
@@ -575,22 +670,22 @@ async function downloadTemplate(templateDir, dest, ref) {
575
670
  }
576
671
  });
577
672
  } finally {
578
- if (fs3.existsSync(tempFile)) {
579
- fs3.unlinkSync(tempFile);
673
+ if (fs2.existsSync(tempFile)) {
674
+ fs2.unlinkSync(tempFile);
580
675
  }
581
676
  }
582
- const files = fs3.readdirSync(dest);
677
+ const files = fs2.readdirSync(dest);
583
678
  if (files.length === 0) {
584
679
  throw new Error(`Template "${templateDir}" not found in repository`);
585
680
  }
586
681
  }
587
682
  function updatePackageJson(dest, projectName) {
588
- const pkgPath = path3.join(dest, "package.json");
589
- const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
683
+ const pkgPath = path2.join(dest, "package.json");
684
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
590
685
  pkg.name = projectName;
591
686
  pkg.version = "0.0.1";
592
687
  pkg.private = true;
593
- fs3.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
688
+ fs2.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
594
689
  `);
595
690
  }
596
691
  async function fetchLatestVersion(packageName) {
@@ -606,8 +701,8 @@ async function fetchLatestVersion(packageName) {
606
701
  return data.version;
607
702
  }
608
703
  async function resolveWorkspaceDeps(dest) {
609
- const pkgPath = path3.join(dest, "package.json");
610
- const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
704
+ const pkgPath = path2.join(dest, "package.json");
705
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
611
706
  const depSections = [
612
707
  "dependencies",
613
708
  "devDependencies",
@@ -644,29 +739,29 @@ async function resolveWorkspaceDeps(dest) {
644
739
  pkg[section][name] = version;
645
740
  }
646
741
  }
647
- fs3.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
742
+ fs2.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
648
743
  `);
649
744
  return workspaceDeps.size;
650
745
  }
651
746
  function copyEnvExample(dest) {
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);
747
+ const examplePath = path2.join(dest, ".env.example");
748
+ const envPath = path2.join(dest, ".env");
749
+ if (fs2.existsSync(examplePath)) {
750
+ fs2.copyFileSync(examplePath, envPath);
656
751
  }
657
752
  }
658
753
  function writeApiKeyToEnv(dest, apiKey) {
659
- const envPath = path3.join(dest, ".env");
660
- if (!fs3.existsSync(envPath)) return;
661
- let content = fs3.readFileSync(envPath, "utf-8");
754
+ const envPath = path2.join(dest, ".env");
755
+ if (!fs2.existsSync(envPath)) return;
756
+ let content = fs2.readFileSync(envPath, "utf-8");
662
757
  content = content.replace(/COMMET_API_KEY=.*/, `COMMET_API_KEY=${apiKey}`);
663
- fs3.writeFileSync(envPath, content);
758
+ fs2.writeFileSync(envPath, content);
664
759
  }
665
760
  function linkProject(dest, orgId, orgName) {
666
- const commetDir = path3.join(dest, ".commet");
667
- fs3.mkdirSync(commetDir, { recursive: true });
668
- fs3.writeFileSync(
669
- path3.join(commetDir, "config.json"),
761
+ const commetDir = path2.join(dest, ".commet");
762
+ fs2.mkdirSync(commetDir, { recursive: true });
763
+ fs2.writeFileSync(
764
+ path2.join(commetDir, "config.json"),
670
765
  JSON.stringify({ orgId, orgName, mode: "sandbox" }, null, 2),
671
766
  "utf8"
672
767
  );
@@ -698,8 +793,8 @@ async function installSkills(projectRoot) {
698
793
  );
699
794
  child.on("close", (code) => {
700
795
  if (code !== 0) {
701
- console.log(import_chalk3.default.dim(" You can install them manually by running:"));
702
- console.log(import_chalk3.default.dim(" npx skills add commet-labs/commet-skills"));
796
+ console.log(import_chalk5.default.dim(" You can install them manually by running:"));
797
+ console.log(import_chalk5.default.dim(" npx skills add commet-labs/commet-skills"));
703
798
  }
704
799
  resolve4();
705
800
  });
@@ -710,10 +805,10 @@ var createCommand = new import_commander2.Command("create").description("Create
710
805
  "Template to use (fixed, seats, metered, credits, balance-ai, balance-fixed)"
711
806
  ).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) => {
712
807
  if (opts.list) {
713
- console.log(import_chalk3.default.bold("\nAvailable templates:\n"));
808
+ console.log(import_chalk5.default.bold("\nAvailable templates:\n"));
714
809
  for (const t of TEMPLATES) {
715
810
  console.log(
716
- ` ${commetColor(t.name.padEnd(14))}${import_chalk3.default.dim(t.description)}`
811
+ ` ${commetColor(t.name.padEnd(14))}${import_chalk5.default.dim(t.description)}`
717
812
  );
718
813
  }
719
814
  console.log();
@@ -722,9 +817,9 @@ var createCommand = new import_commander2.Command("create").description("Create
722
817
  let projectName = argName;
723
818
  if (!projectName) {
724
819
  if (!isInteractive()) {
725
- console.log(import_chalk3.default.red("\u2717 Project name is required"));
820
+ console.log(import_chalk5.default.red("\u2717 Project name is required"));
726
821
  console.log(
727
- import_chalk3.default.dim("Pass as positional argument: commet create <name>")
822
+ import_chalk5.default.dim("Pass as positional argument: commet create <name>")
728
823
  );
729
824
  return;
730
825
  }
@@ -734,21 +829,21 @@ var createCommand = new import_commander2.Command("create").description("Create
734
829
  theme: promptTheme
735
830
  });
736
831
  } catch {
737
- console.log(import_chalk3.default.yellow("\n\u26A0 Cancelled"));
832
+ console.log(import_chalk5.default.yellow("\n\u26A0 Cancelled"));
738
833
  return;
739
834
  }
740
835
  }
741
- const dest = path3.resolve(projectName);
742
- if (fs3.existsSync(dest)) {
836
+ const dest = path2.resolve(projectName);
837
+ if (fs2.existsSync(dest)) {
743
838
  console.log(
744
- import_chalk3.default.red(`\u2717 Directory "${projectName}" already exists`)
839
+ import_chalk5.default.red(`\u2717 Directory "${projectName}" already exists`)
745
840
  );
746
841
  return;
747
842
  }
748
843
  if (!authExists()) {
749
844
  if (!isInteractive()) {
750
- console.log(import_chalk3.default.red("\u2717 Not authenticated"));
751
- console.log(import_chalk3.default.dim("Run `commet login` first"));
845
+ console.log(import_chalk5.default.red("\u2717 Not authenticated"));
846
+ console.log(import_chalk5.default.dim("Run `commet login` first"));
752
847
  return;
753
848
  }
754
849
  const loggedIn = await performLogin();
@@ -756,11 +851,11 @@ var createCommand = new import_commander2.Command("create").description("Create
756
851
  return;
757
852
  }
758
853
  }
759
- const orgsSpinner = (0, import_ora2.default)("Fetching sandbox organizations...").start();
854
+ const orgsSpinner = (0, import_ora3.default)("Fetching sandbox organizations...").start();
760
855
  const orgsResult = await apiRequest(`${BASE_URL}/api/cli/organizations`);
761
856
  if (orgsResult.error || !orgsResult.data) {
762
857
  orgsSpinner.fail("Failed to fetch organizations");
763
- console.log(import_chalk3.default.dim(orgsResult.error));
858
+ console.log(import_chalk5.default.dim(orgsResult.error));
764
859
  return;
765
860
  }
766
861
  const organizations = orgsResult.data.organizations.filter(
@@ -768,9 +863,9 @@ var createCommand = new import_commander2.Command("create").description("Create
768
863
  );
769
864
  orgsSpinner.stop();
770
865
  if (organizations.length === 0) {
771
- console.log(import_chalk3.default.yellow("\u26A0 No sandbox organizations found"));
866
+ console.log(import_chalk5.default.yellow("\u26A0 No sandbox organizations found"));
772
867
  console.log(
773
- import_chalk3.default.dim("Create an organization at https://commet.co first")
868
+ import_chalk5.default.dim("Create an organization at https://commet.co first")
774
869
  );
775
870
  return;
776
871
  }
@@ -780,9 +875,9 @@ var createCommand = new import_commander2.Command("create").description("Create
780
875
  (org) => org.slug === opts.org || org.id === opts.org
781
876
  );
782
877
  if (!match) {
783
- console.log(import_chalk3.default.red(`\u2717 Organization "${opts.org}" not found`));
878
+ console.log(import_chalk5.default.red(`\u2717 Organization "${opts.org}" not found`));
784
879
  console.log(
785
- import_chalk3.default.dim(
880
+ import_chalk5.default.dim(
786
881
  `Available: ${organizations.map((o) => o.slug).join(", ")}`
787
882
  )
788
883
  );
@@ -793,9 +888,9 @@ var createCommand = new import_commander2.Command("create").description("Create
793
888
  selectedOrg = organizations[0];
794
889
  } else {
795
890
  if (!isInteractive()) {
796
- console.log(import_chalk3.default.red("\u2717 Organization is required"));
891
+ console.log(import_chalk5.default.red("\u2717 Organization is required"));
797
892
  console.log(
798
- import_chalk3.default.dim(
893
+ import_chalk5.default.dim(
799
894
  `Pass --org=<slug>. Available: ${organizations.map((o) => o.slug).join(", ")}`
800
895
  )
801
896
  );
@@ -805,30 +900,30 @@ var createCommand = new import_commander2.Command("create").description("Create
805
900
  const orgId = await (0, import_prompts.select)({
806
901
  message: "Sandbox organization:",
807
902
  choices: organizations.map((org) => ({
808
- name: `${org.name} ${import_chalk3.default.dim(`(${org.slug})`)}`,
903
+ name: `${org.name} ${import_chalk5.default.dim(`(${org.slug})`)}`,
809
904
  value: org.id
810
905
  })),
811
906
  theme: promptTheme
812
907
  });
813
908
  selectedOrg = organizations.find((org) => org.id === orgId);
814
909
  } catch {
815
- console.log(import_chalk3.default.yellow("\n\u26A0 Cancelled"));
910
+ console.log(import_chalk5.default.yellow("\n\u26A0 Cancelled"));
816
911
  return;
817
912
  }
818
913
  }
819
914
  let template = TEMPLATES.find((t) => t.name === opts.template);
820
915
  if (opts.template && !template) {
821
- console.log(import_chalk3.default.red(`\u2717 Unknown template "${opts.template}"`));
916
+ console.log(import_chalk5.default.red(`\u2717 Unknown template "${opts.template}"`));
822
917
  console.log(
823
- import_chalk3.default.dim("Run `commet create --list` to see available templates")
918
+ import_chalk5.default.dim("Run `commet create --list` to see available templates")
824
919
  );
825
920
  return;
826
921
  }
827
922
  if (!template) {
828
923
  if (!isInteractive()) {
829
- console.log(import_chalk3.default.red("\u2717 Template is required"));
924
+ console.log(import_chalk5.default.red("\u2717 Template is required"));
830
925
  console.log(
831
- import_chalk3.default.dim(
926
+ import_chalk5.default.dim(
832
927
  `Pass --template=<name>. Available: ${TEMPLATES.map((t) => t.name).join(", ")}`
833
928
  )
834
929
  );
@@ -838,35 +933,35 @@ var createCommand = new import_commander2.Command("create").description("Create
838
933
  const selected = await (0, import_prompts.select)({
839
934
  message: "Billing model:",
840
935
  choices: TEMPLATES.map((t) => ({
841
- name: `${t.name.padEnd(14)} ${import_chalk3.default.dim(t.description)}`,
936
+ name: `${t.name.padEnd(14)} ${import_chalk5.default.dim(t.description)}`,
842
937
  value: t.name
843
938
  })),
844
939
  theme: promptTheme
845
940
  });
846
941
  template = TEMPLATES.find((t) => t.name === selected);
847
942
  } catch {
848
- console.log(import_chalk3.default.yellow("\n\u26A0 Cancelled"));
943
+ console.log(import_chalk5.default.yellow("\n\u26A0 Cancelled"));
849
944
  return;
850
945
  }
851
946
  }
852
947
  const shouldInstallSkills = await resolveSkills(opts);
853
- const downloadSpinner = (0, import_ora2.default)("Downloading template...").start();
948
+ const downloadSpinner = (0, import_ora3.default)("Downloading template...").start();
854
949
  try {
855
950
  await downloadTemplate(template.dir, dest, opts.ref);
856
951
  downloadSpinner.succeed("Template downloaded");
857
952
  } catch (error) {
858
953
  downloadSpinner.fail("Failed to download template");
859
954
  if (error instanceof Error) {
860
- console.error(import_chalk3.default.red(error.message));
955
+ console.error(import_chalk5.default.red(error.message));
861
956
  }
862
- if (fs3.existsSync(dest)) {
863
- fs3.rmSync(dest, { recursive: true, force: true });
957
+ if (fs2.existsSync(dest)) {
958
+ fs2.rmSync(dest, { recursive: true, force: true });
864
959
  }
865
960
  return;
866
961
  }
867
962
  updatePackageJson(dest, projectName);
868
963
  copyEnvExample(dest);
869
- const resolveSpinner = (0, import_ora2.default)("Resolving package versions...").start();
964
+ const resolveSpinner = (0, import_ora3.default)("Resolving package versions...").start();
870
965
  try {
871
966
  const count = await resolveWorkspaceDeps(dest);
872
967
  if (count > 0) {
@@ -877,14 +972,14 @@ var createCommand = new import_commander2.Command("create").description("Create
877
972
  } catch (error) {
878
973
  resolveSpinner.fail("Failed to resolve package versions");
879
974
  if (error instanceof Error) {
880
- console.error(import_chalk3.default.red(error.message));
975
+ console.error(import_chalk5.default.red(error.message));
881
976
  }
882
- if (fs3.existsSync(dest)) {
883
- fs3.rmSync(dest, { recursive: true, force: true });
977
+ if (fs2.existsSync(dest)) {
978
+ fs2.rmSync(dest, { recursive: true, force: true });
884
979
  }
885
980
  return;
886
981
  }
887
- const planSpinner = (0, import_ora2.default)("Creating plans...").start();
982
+ const planSpinner = (0, import_ora3.default)("Creating plans...").start();
888
983
  const templateResult = await apiRequest(`${BASE_URL}/api/cli/templates`, {
889
984
  method: "POST",
890
985
  body: JSON.stringify({
@@ -894,13 +989,13 @@ var createCommand = new import_commander2.Command("create").description("Create
894
989
  });
895
990
  if (templateResult.error || !templateResult.data) {
896
991
  planSpinner.fail("Failed to create plans");
897
- console.log(import_chalk3.default.dim(templateResult.error));
992
+ console.log(import_chalk5.default.dim(templateResult.error));
898
993
  } else {
899
994
  planSpinner.succeed(
900
995
  `Created ${templateResult.data.plansCreated} plans and ${templateResult.data.featuresCreated} features`
901
996
  );
902
997
  }
903
- const keySpinner = (0, import_ora2.default)("Creating API key...").start();
998
+ const keySpinner = (0, import_ora3.default)("Creating API key...").start();
904
999
  const keyResult = await apiRequest(`${BASE_URL}/api/cli/api-keys`, {
905
1000
  method: "POST",
906
1001
  body: JSON.stringify({
@@ -910,8 +1005,8 @@ var createCommand = new import_commander2.Command("create").description("Create
910
1005
  });
911
1006
  if (keyResult.error || !keyResult.data) {
912
1007
  keySpinner.fail("Failed to create API key");
913
- console.log(import_chalk3.default.dim(keyResult.error));
914
- console.log(import_chalk3.default.dim("You can create one manually in the dashboard"));
1008
+ console.log(import_chalk5.default.dim(keyResult.error));
1009
+ console.log(import_chalk5.default.dim("You can create one manually in the dashboard"));
915
1010
  } else {
916
1011
  writeApiKeyToEnv(dest, keyResult.data.apiKey);
917
1012
  keySpinner.succeed("API key created and saved to .env");
@@ -920,10 +1015,10 @@ var createCommand = new import_commander2.Command("create").description("Create
920
1015
  if (shouldInstallSkills) {
921
1016
  await installSkills(dest);
922
1017
  }
923
- console.log(import_chalk3.default.green(`
1018
+ console.log(import_chalk5.default.green(`
924
1019
  \u2713 Created ${projectName}`));
925
- console.log(import_chalk3.default.dim(` Template: ${template.name}`));
926
- console.log(import_chalk3.default.dim(` Organization: ${selectedOrg.name} \xB7 sandbox`));
1020
+ console.log(import_chalk5.default.dim(` Template: ${template.name}`));
1021
+ console.log(import_chalk5.default.dim(` Organization: ${selectedOrg.name} \xB7 sandbox`));
927
1022
  console.log();
928
1023
  console.log(` ${commetColor("cd")} ${projectName}`);
929
1024
  console.log(` ${commetColor("npm install")}`);
@@ -933,20 +1028,20 @@ var createCommand = new import_commander2.Command("create").description("Create
933
1028
 
934
1029
  // src/commands/link.ts
935
1030
  var import_prompts2 = require("@inquirer/prompts");
936
- var import_chalk4 = __toESM(require("chalk"));
1031
+ var import_chalk6 = __toESM(require("chalk"));
937
1032
  var import_commander3 = require("commander");
938
- var import_ora3 = __toESM(require("ora"));
1033
+ var import_ora4 = __toESM(require("ora"));
939
1034
 
940
1035
  // src/utils/gitignore-updater.ts
941
- var fs4 = __toESM(require("fs"));
942
- var path4 = __toESM(require("path"));
1036
+ var fs3 = __toESM(require("fs"));
1037
+ var path3 = __toESM(require("path"));
943
1038
  function updateGitignore(entry) {
944
1039
  const cwd = process.cwd();
945
- const gitignorePath = path4.join(cwd, ".gitignore");
1040
+ const gitignorePath = path3.join(cwd, ".gitignore");
946
1041
  try {
947
1042
  let content = "";
948
- if (fs4.existsSync(gitignorePath)) {
949
- content = fs4.readFileSync(gitignorePath, "utf8");
1043
+ if (fs3.existsSync(gitignorePath)) {
1044
+ content = fs3.readFileSync(gitignorePath, "utf8");
950
1045
  const lines = content.split("\n");
951
1046
  for (const line of lines) {
952
1047
  const trimmed = line.trim();
@@ -960,7 +1055,7 @@ function updateGitignore(entry) {
960
1055
  }
961
1056
  content += `${entry}
962
1057
  `;
963
- fs4.writeFileSync(gitignorePath, content, "utf8");
1058
+ fs3.writeFileSync(gitignorePath, content, "utf8");
964
1059
  return { success: true };
965
1060
  } catch (error) {
966
1061
  return {
@@ -972,285 +1067,211 @@ function updateGitignore(entry) {
972
1067
 
973
1068
  // src/commands/link.ts
974
1069
  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."
1070
+ "Link this project to a Commet organization. Re-run to switch orgs."
976
1071
  ).option(
977
1072
  "--org <slug-or-id>",
978
1073
  "Organization slug or ID \u2014 skips interactive selection"
1074
+ ).option("--clear", "Unlink project from its organization").option(
1075
+ "--output <format>",
1076
+ "Output format: human (default) or agent",
1077
+ "human"
979
1078
  ).addHelpText(
980
1079
  "after",
981
1080
  `
982
1081
  Examples:
983
1082
  $ commet link Interactive \u2014 choose from a list
984
1083
  $ commet link --org acme Non-interactive \u2014 match by slug or ID
985
-
986
- Use 'commet orgs' to see available organizations and their slugs.
1084
+ $ commet link --org other Re-run to switch organization
1085
+ $ commet link --clear Unlink project
987
1086
  `
988
1087
  ).action(async (options) => {
989
- if (!authExists()) {
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);
993
- }
994
- if (projectConfigExists()) {
995
- const config = loadProjectConfig();
996
- console.log(import_chalk4.default.yellow("\u26A0 This project is already linked"));
997
- console.log(
998
- import_chalk4.default.dim(`Organization: ${config?.orgName} \xB7 ${config?.mode}`)
999
- );
1000
- console.log(
1001
- import_chalk4.default.dim(
1002
- "\nRun `commet unlink` first if you want to change the organization"
1003
- )
1004
- );
1005
- return;
1006
- }
1007
- const spinner = (0, import_ora3.default)("Fetching organizations...").start();
1008
- const result = await apiRequest(
1009
- `${BASE_URL}/api/cli/organizations`
1010
- );
1011
- if (result.error || !result.data) {
1012
- spinner.fail("Failed to fetch organizations");
1013
- console.error(import_chalk4.default.red("Error:"), result.error);
1014
- process.exit(1);
1015
- }
1016
- const { organizations } = result.data;
1017
- if (organizations.length === 0) {
1018
- spinner.stop();
1019
- console.log(import_chalk4.default.yellow("\u26A0 No organizations found"));
1020
- console.log(
1021
- import_chalk4.default.dim("Create an organization at https://commet.co first")
1022
- );
1023
- return;
1024
- }
1025
- spinner.stop();
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})`));
1088
+ const agentMode = isAgentMode(options);
1089
+ if (options.clear) {
1090
+ if (!projectConfigExists()) {
1091
+ if (agentMode) {
1092
+ console.log(JSON.stringify({ success: true, message: "Not linked" }));
1093
+ } else {
1094
+ console.log(
1095
+ import_chalk6.default.yellow("\u26A0 This project is not linked to any organization")
1096
+ );
1036
1097
  }
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
1098
  return;
1053
1099
  }
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
- }
1059
- }
1060
- saveProjectConfig({
1061
- orgId: selectedOrg.id,
1062
- orgName: selectedOrg.name,
1063
- mode: selectedOrg.mode
1064
- });
1065
- const gitignoreResult = updateGitignore(".commet/");
1066
- console.log(import_chalk4.default.green("\n\u2713 Project linked successfully"));
1067
- console.log(
1068
- import_chalk4.default.dim(`Organization: ${selectedOrg.name} \xB7 ${selectedOrg.mode}`)
1069
- );
1070
- if (gitignoreResult.success) {
1071
- console.log(import_chalk4.default.green("\u2713 Updated .gitignore"));
1072
- } else {
1073
- console.log(import_chalk4.default.yellow("\u26A0 No .gitignore found"));
1074
- console.log(import_chalk4.default.dim("Add .commet/ to your .gitignore file"));
1075
- }
1076
- console.log(import_chalk4.default.dim("\nRun 'commet pull' to generate TypeScript types"));
1077
- });
1078
-
1079
- // src/commands/list.ts
1080
- var import_chalk5 = __toESM(require("chalk"));
1081
- var import_commander4 = require("commander");
1082
- var import_ora4 = __toESM(require("ora"));
1083
- var validTypes = ["features", "seats", "plans"];
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;
1097
- if (!validTypes.includes(type)) {
1098
- if (jsonMode) {
1099
- console.log(
1100
- JSON.stringify({
1101
- error: `Invalid type "${type}". Use: features, seats, or plans`
1102
- })
1103
- );
1100
+ clearProjectConfig();
1101
+ if (agentMode) {
1102
+ console.log(JSON.stringify({ success: true, action: "unlinked" }));
1104
1103
  } 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"));
1104
+ console.log(import_chalk6.default.green("\u2713 Project unlinked"));
1112
1105
  }
1113
- process.exit(1);
1106
+ return;
1114
1107
  }
1115
1108
  if (!authExists()) {
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);
1123
- }
1124
- if (!projectConfigExists()) {
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);
1134
- }
1135
- const projectConfig = loadProjectConfig();
1136
- if (!projectConfig) {
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);
1109
+ exitWithError({
1110
+ code: "auth_required",
1111
+ message: "Not authenticated",
1112
+ action: "commet login"
1113
+ });
1143
1114
  }
1144
- const spinner = jsonMode ? null : (0, import_ora4.default)(`Fetching ${type}...`).start();
1115
+ const currentConfig = projectConfigExists() ? loadProjectConfig() : null;
1116
+ const spinner = agentMode ? null : (0, import_ora4.default)("Fetching organizations...").start();
1145
1117
  const result = await apiRequest(
1146
- `${BASE_URL}/api/cli/pull?orgId=${projectConfig.orgId}`
1118
+ `${BASE_URL}/api/cli/organizations`
1147
1119
  );
1148
1120
  if (result.error || !result.data) {
1149
- if (jsonMode) {
1121
+ if (agentMode) {
1150
1122
  console.log(JSON.stringify({ error: result.error }));
1151
1123
  } else {
1152
- spinner?.fail(`Failed to fetch ${type}`);
1153
- console.error(import_chalk5.default.red("Error:"), result.error);
1124
+ spinner?.fail("Failed to fetch organizations");
1125
+ console.error(import_chalk6.default.red("Error:"), result.error?.message);
1154
1126
  }
1155
1127
  process.exit(1);
1156
1128
  }
1157
- spinner?.stop();
1158
- if (type === "features") {
1159
- const { features } = result.data;
1160
- if (jsonMode) {
1161
- console.log(JSON.stringify(features));
1162
- return;
1163
- }
1164
- if (features.length === 0) {
1165
- console.log(import_chalk5.default.yellow("\u26A0 No features found"));
1129
+ const { organizations } = result.data;
1130
+ if (organizations.length === 0) {
1131
+ spinner?.stop();
1132
+ if (agentMode) {
1166
1133
  console.log(
1167
- import_chalk5.default.dim("Create features in your Commet dashboard first")
1134
+ JSON.stringify({
1135
+ error: {
1136
+ code: "no_organizations",
1137
+ message: "No organizations found"
1138
+ }
1139
+ })
1168
1140
  );
1169
- return;
1170
- }
1171
- console.log(import_chalk5.default.bold(`
1172
- Features (${features.length})
1173
- `));
1174
- for (const feature of features) {
1141
+ } else {
1142
+ console.log(import_chalk6.default.yellow("\u26A0 No organizations found"));
1175
1143
  console.log(
1176
- import_chalk5.default.green(` ${feature.code} ${import_chalk5.default.dim(`(${feature.type})`)}`)
1144
+ import_chalk6.default.dim("Create an organization at https://commet.co first")
1177
1145
  );
1178
- console.log(import_chalk5.default.dim(` ${feature.name}`));
1179
- if (feature.description) {
1180
- console.log(import_chalk5.default.dim(` ${feature.description}`));
1146
+ }
1147
+ return;
1148
+ }
1149
+ spinner?.stop();
1150
+ let selectedOrg;
1151
+ if (options.org) {
1152
+ selectedOrg = organizations.find(
1153
+ (org) => org.slug === options.org || org.id === options.org
1154
+ );
1155
+ if (!selectedOrg) {
1156
+ if (agentMode) {
1157
+ console.log(
1158
+ JSON.stringify({
1159
+ error: {
1160
+ code: "org_not_found",
1161
+ message: `Organization "${options.org}" not found`
1162
+ },
1163
+ organizations: organizations.map((o) => ({
1164
+ slug: o.slug,
1165
+ mode: o.mode
1166
+ }))
1167
+ })
1168
+ );
1169
+ } else {
1170
+ console.log(import_chalk6.default.red(`\u2717 Organization "${options.org}" not found`));
1171
+ console.log(import_chalk6.default.dim("\nAvailable organizations:"));
1172
+ for (const org of organizations) {
1173
+ console.log(import_chalk6.default.dim(` ${org.slug} (${org.mode})`));
1174
+ }
1181
1175
  }
1182
- console.log("");
1176
+ process.exit(1);
1183
1177
  }
1184
- } else if (type === "seats") {
1185
- const { seatTypes } = result.data;
1186
- if (jsonMode) {
1187
- console.log(JSON.stringify(seatTypes));
1178
+ } else {
1179
+ let orgId;
1180
+ try {
1181
+ orgId = await (0, import_prompts2.select)({
1182
+ message: currentConfig ? `Switch from ${currentConfig.orgName}? Select new org:` : "Select organization:",
1183
+ choices: organizations.map((org) => {
1184
+ const isCurrent = currentConfig?.orgId === org.id;
1185
+ const label = isCurrent ? `${org.name} ${import_chalk6.default.dim(`(${org.slug}) \xB7 ${org.mode}`)} ${import_chalk6.default.green("\u2190 current")}` : `${org.name} ${import_chalk6.default.dim(`(${org.slug}) \xB7 ${org.mode}`)}`;
1186
+ return { name: label, value: org.id };
1187
+ }),
1188
+ theme: promptTheme
1189
+ });
1190
+ } catch (_error) {
1191
+ console.log(import_chalk6.default.yellow("\n\u26A0 Cancelled"));
1188
1192
  return;
1189
1193
  }
1190
- if (seatTypes.length === 0) {
1191
- console.log(import_chalk5.default.yellow("\u26A0 No seat types found"));
1194
+ selectedOrg = organizations.find((org) => org.id === orgId);
1195
+ if (!selectedOrg) {
1196
+ exitWithError({
1197
+ code: "org_not_found",
1198
+ message: "Organization not found"
1199
+ });
1200
+ }
1201
+ }
1202
+ if (currentConfig?.orgId === selectedOrg.id) {
1203
+ if (agentMode) {
1192
1204
  console.log(
1193
- import_chalk5.default.dim("Create seat types in your Commet dashboard first")
1205
+ JSON.stringify({
1206
+ success: true,
1207
+ action: "unchanged",
1208
+ organization: {
1209
+ id: selectedOrg.id,
1210
+ name: selectedOrg.name,
1211
+ slug: selectedOrg.slug,
1212
+ mode: selectedOrg.mode
1213
+ }
1214
+ })
1194
1215
  );
1195
- return;
1196
- }
1197
- console.log(import_chalk5.default.bold(`
1198
- Seat Types (${seatTypes.length})
1199
- `));
1200
- for (const seatType of seatTypes) {
1216
+ } else {
1201
1217
  console.log(
1202
- import_chalk5.default.green(
1203
- ` ${seatType.code}${seatType.isFree ? import_chalk5.default.dim(" (free)") : ""}`
1218
+ import_chalk6.default.green(
1219
+ `\u2713 Already linked to ${selectedOrg.name} (${selectedOrg.mode})`
1204
1220
  )
1205
1221
  );
1206
- console.log(import_chalk5.default.dim(` ${seatType.name}`));
1207
- if (seatType.description) {
1208
- console.log(import_chalk5.default.dim(` ${seatType.description}`));
1209
- }
1210
- console.log("");
1211
1222
  }
1223
+ return;
1224
+ }
1225
+ const action = currentConfig ? "switched" : "linked";
1226
+ saveProjectConfig({
1227
+ orgId: selectedOrg.id,
1228
+ orgName: selectedOrg.name,
1229
+ mode: selectedOrg.mode
1230
+ });
1231
+ const gitignoreResult = updateGitignore(".commet/");
1232
+ if (agentMode) {
1233
+ console.log(
1234
+ JSON.stringify({
1235
+ success: true,
1236
+ action,
1237
+ organization: {
1238
+ id: selectedOrg.id,
1239
+ name: selectedOrg.name,
1240
+ slug: selectedOrg.slug,
1241
+ mode: selectedOrg.mode
1242
+ }
1243
+ })
1244
+ );
1212
1245
  } else {
1213
- const { plans } = result.data;
1214
- if (jsonMode) {
1215
- console.log(JSON.stringify(plans));
1216
- return;
1217
- }
1218
- if (plans.length === 0) {
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"));
1221
- return;
1222
- }
1223
- console.log(import_chalk5.default.bold(`
1224
- Plans (${plans.length})
1225
- `));
1226
- for (const plan of plans) {
1227
- console.log(import_chalk5.default.green(` ${plan.code}`));
1228
- console.log(import_chalk5.default.dim(` ${plan.name}`));
1229
- if (plan.description) {
1230
- console.log(import_chalk5.default.dim(` ${plan.description}`));
1231
- }
1232
- console.log("");
1246
+ const verb = action === "switched" ? "Switched to" : "Linked to";
1247
+ console.log(
1248
+ import_chalk6.default.green(`
1249
+ \u2713 ${verb} ${selectedOrg.name} (${selectedOrg.mode})`)
1250
+ );
1251
+ if (gitignoreResult.success && action === "linked") {
1252
+ console.log(import_chalk6.default.green("\u2713 Updated .gitignore"));
1233
1253
  }
1254
+ console.log(import_chalk6.default.dim("\nRun `commet pull` to sync your config"));
1234
1255
  }
1235
1256
  });
1236
1257
 
1237
1258
  // src/commands/listen.ts
1238
1259
  var import_ably = __toESM(require("ably"));
1239
- var import_chalk6 = __toESM(require("chalk"));
1240
- var import_commander5 = require("commander");
1260
+ var import_chalk7 = __toESM(require("chalk"));
1261
+ var import_commander4 = require("commander");
1241
1262
  function printEventLine(line) {
1242
1263
  const time = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
1243
- const eventName = import_chalk6.default.yellow(line.event.padEnd(28));
1244
- const timing = import_chalk6.default.dim(`(${line.ms}ms)`);
1264
+ const eventName = import_chalk7.default.yellow(line.event.padEnd(28));
1265
+ const timing = import_chalk7.default.dim(`(${line.ms}ms)`);
1245
1266
  if ("error" in line) {
1246
1267
  console.log(
1247
- ` ${import_chalk6.default.dim(time)} ${eventName} \u2192 ${import_chalk6.default.red("Error")} ${timing}`
1268
+ ` ${import_chalk7.default.dim(time)} ${eventName} \u2192 ${import_chalk7.default.red("Error")} ${timing}`
1248
1269
  );
1249
- console.log(` ${" ".repeat(12)}${import_chalk6.default.red(line.error)}`);
1270
+ console.log(` ${" ".repeat(12)}${import_chalk7.default.red(line.error)}`);
1250
1271
  return;
1251
1272
  }
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}`);
1273
+ const status = line.statusCode < 400 ? import_chalk7.default.green(`${line.statusCode} OK`) : import_chalk7.default.red(`${line.statusCode} Error`);
1274
+ console.log(` ${import_chalk7.default.dim(time)} ${eventName} \u2192 ${status} ${timing}`);
1254
1275
  }
1255
1276
  function isListenMessage(data) {
1256
1277
  if (typeof data !== "object" || data === null) return false;
@@ -1273,7 +1294,7 @@ function resolveTargetUrl(input2) {
1273
1294
  }
1274
1295
  return parsed.toString();
1275
1296
  }
1276
- var listenCommand = new import_commander5.Command("listen").description(
1297
+ var listenCommand = new import_commander4.Command("listen").description(
1277
1298
  "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
1299
  ).argument(
1279
1300
  "<url>",
@@ -1290,60 +1311,35 @@ Examples:
1290
1311
  $ commet listen 3000 --events invoice.paid Only forward invoice.paid events
1291
1312
  `
1292
1313
  ).action(async (url, options) => {
1293
- const auth = loadAuth();
1294
- if (!auth) {
1295
- console.log(import_chalk6.default.red("Not authenticated. Run: commet login"));
1296
- process.exit(1);
1297
- }
1298
- const projectConfig = loadProjectConfig();
1299
- if (!projectConfig) {
1300
- console.log(import_chalk6.default.red("No project linked. Run: commet link"));
1301
- process.exit(1);
1302
- }
1314
+ const { orgId } = requireOrgContext();
1303
1315
  let targetUrl;
1304
1316
  try {
1305
1317
  targetUrl = resolveTargetUrl(url);
1306
1318
  } catch (error) {
1307
- console.log(
1308
- import_chalk6.default.red(error instanceof Error ? error.message : "Invalid URL")
1309
- );
1310
- process.exit(1);
1319
+ exitWithError({
1320
+ code: "invalid_url",
1321
+ message: error instanceof Error ? error.message : "Invalid URL"
1322
+ });
1311
1323
  }
1312
- async function fetchTokenRequest() {
1313
- const result = await apiRequest(
1314
- `${BASE_URL}/api/cli/listen/start`,
1315
- {
1316
- method: "POST",
1317
- body: JSON.stringify({ organizationId: projectConfig.orgId })
1318
- }
1319
- );
1320
- if (result.error || !result.data) {
1321
- console.log(import_chalk6.default.red("Failed to start listen session"));
1322
- if (result.error) {
1323
- console.log(import_chalk6.default.dim(result.error));
1324
- }
1325
- process.exit(1);
1326
- }
1327
- return result.data;
1328
- }
1329
- const initialSession = await fetchTokenRequest();
1330
- const { sessionId, channelName, signingSecret, tokenRequest } = initialSession;
1331
- async function refreshToken() {
1332
- const result = await apiRequest(
1333
- `${BASE_URL}/api/cli/listen/refresh`,
1334
- {
1335
- method: "POST",
1336
- body: JSON.stringify({
1337
- sessionId,
1338
- organizationId: projectConfig.orgId
1339
- })
1340
- }
1341
- );
1342
- if (result.error || !result.data) {
1343
- throw new Error(result.error ?? "Failed to refresh token");
1324
+ const projectConfig = loadProjectConfig();
1325
+ const organizationId = orgId === "__from_api_key__" ? void 0 : orgId;
1326
+ const orgName = projectConfig?.orgName ?? "API key";
1327
+ const startResult = await apiRequest(
1328
+ `${BASE_URL}/api/cli/listen/start`,
1329
+ {
1330
+ method: "POST",
1331
+ body: JSON.stringify({
1332
+ ...organizationId ? { organizationId } : {}
1333
+ })
1344
1334
  }
1345
- return result.data.tokenRequest;
1335
+ );
1336
+ if (startResult.error || !startResult.data) {
1337
+ exitWithError({
1338
+ code: "listen_failed",
1339
+ message: "Failed to start listen session"
1340
+ });
1346
1341
  }
1342
+ const { sessionId, channelName, signingSecret, tokenRequest } = startResult.data;
1347
1343
  let isFirstToken = true;
1348
1344
  const ably = new import_ably.default.Realtime({
1349
1345
  authCallback: async (_tokenParams, callback) => {
@@ -1353,8 +1349,20 @@ Examples:
1353
1349
  callback(null, tokenRequest);
1354
1350
  return;
1355
1351
  }
1356
- const refreshed = await refreshToken();
1357
- callback(null, refreshed);
1352
+ const refreshResult = await apiRequest(
1353
+ `${BASE_URL}/api/cli/listen/refresh`,
1354
+ {
1355
+ method: "POST",
1356
+ body: JSON.stringify({
1357
+ sessionId,
1358
+ ...organizationId ? { organizationId } : {}
1359
+ })
1360
+ }
1361
+ );
1362
+ if (refreshResult.error || !refreshResult.data) {
1363
+ throw new Error("Failed to refresh token");
1364
+ }
1365
+ callback(null, refreshResult.data.tokenRequest);
1358
1366
  } catch (error) {
1359
1367
  callback(
1360
1368
  error instanceof Error ? error.message : "Token refresh failed",
@@ -1366,27 +1374,25 @@ Examples:
1366
1374
  let wasConnected = false;
1367
1375
  ably.connection.on("connected", () => {
1368
1376
  if (wasConnected) {
1369
- console.log(import_chalk6.default.green(" \u2713 Reconnected"));
1377
+ console.log(import_chalk7.default.green(" \u2713 Reconnected"));
1370
1378
  }
1371
1379
  wasConnected = true;
1372
1380
  });
1373
1381
  ably.connection.on("disconnected", () => {
1374
- console.log(import_chalk6.default.yellow("\n \u26A0 Disconnected. Reconnecting..."));
1382
+ console.log(import_chalk7.default.yellow("\n \u26A0 Disconnected. Reconnecting..."));
1375
1383
  });
1376
1384
  ably.connection.on("failed", () => {
1377
1385
  console.log(
1378
- import_chalk6.default.red("\n \u2717 Connection failed. Check your authentication.")
1386
+ import_chalk7.default.red("\n \u2717 Connection failed. Check your authentication.")
1379
1387
  );
1380
1388
  process.exit(1);
1381
1389
  });
1382
1390
  const channel = ably.channels.get(channelName);
1383
1391
  console.log("");
1384
- console.log(
1385
- import_chalk6.default.green(` \u2713 Authenticated (org: ${projectConfig.orgName})`)
1386
- );
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}`));
1392
+ console.log(import_chalk7.default.green(` \u2713 Authenticated (org: ${orgName})`));
1393
+ console.log(import_chalk7.default.green(" \u2713 Connected to Commet webhook stream"));
1394
+ console.log(import_chalk7.default.cyan(` \u27F6 Forwarding to ${targetUrl}`));
1395
+ console.log(import_chalk7.default.dim(` \u27F6 Signing secret: ${signingSecret}`));
1390
1396
  console.log("");
1391
1397
  console.log(" Ready! Listening for webhook events...");
1392
1398
  console.log("");
@@ -1403,7 +1409,11 @@ Examples:
1403
1409
  body: JSON.stringify(payload)
1404
1410
  }).then((response) => {
1405
1411
  const ms = Math.round(performance.now() - start);
1406
- printEventLine({ event, statusCode: response.status, ms });
1412
+ printEventLine({
1413
+ event,
1414
+ statusCode: response.status,
1415
+ ms
1416
+ });
1407
1417
  }).catch((error) => {
1408
1418
  const ms = Math.round(performance.now() - start);
1409
1419
  printEventLine({
@@ -1417,13 +1427,13 @@ Examples:
1417
1427
  process.on("SIGINT", async () => {
1418
1428
  if (isShuttingDown) return;
1419
1429
  isShuttingDown = true;
1420
- console.log(import_chalk6.default.dim("\n Disconnecting..."));
1430
+ console.log(import_chalk7.default.dim("\n Disconnecting..."));
1421
1431
  ably.close();
1422
1432
  await apiRequest(`${BASE_URL}/api/cli/listen/stop`, {
1423
1433
  method: "POST",
1424
1434
  body: JSON.stringify({
1425
1435
  sessionId,
1426
- organizationId: projectConfig.orgId
1436
+ ...organizationId ? { organizationId } : {}
1427
1437
  })
1428
1438
  });
1429
1439
  process.exit(0);
@@ -1431,15 +1441,28 @@ Examples:
1431
1441
  });
1432
1442
 
1433
1443
  // src/commands/login.ts
1434
- var import_chalk7 = __toESM(require("chalk"));
1435
- var import_commander6 = require("commander");
1436
- var loginCommand = new import_commander6.Command("login").description(
1444
+ var import_chalk8 = __toESM(require("chalk"));
1445
+ var import_commander5 = require("commander");
1446
+ var loginCommand = new import_commander5.Command("login").description(
1437
1447
  "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
1448
  ).action(async () => {
1449
+ if (process.env.COMMET_API_KEY) {
1450
+ if (isAgentMode()) {
1451
+ console.log(
1452
+ JSON.stringify({
1453
+ success: true,
1454
+ message: "Using COMMET_API_KEY \u2014 login not needed"
1455
+ })
1456
+ );
1457
+ } else {
1458
+ console.log(import_chalk8.default.green("\u2713 Using COMMET_API_KEY \u2014 login not needed"));
1459
+ }
1460
+ return;
1461
+ }
1439
1462
  if (authExists()) {
1440
- console.log(import_chalk7.default.yellow("\u26A0 You are already logged in."));
1463
+ console.log(import_chalk8.default.yellow("\u26A0 You are already logged in."));
1441
1464
  console.log(
1442
- import_chalk7.default.dim(
1465
+ import_chalk8.default.dim(
1443
1466
  "Run `commet logout` first if you want to login with a different account."
1444
1467
  )
1445
1468
  );
@@ -1447,9 +1470,9 @@ var loginCommand = new import_commander6.Command("login").description(
1447
1470
  }
1448
1471
  const success = await performLogin();
1449
1472
  if (success) {
1450
- console.log(import_chalk7.default.green("\n\u2713 Authentication complete"));
1473
+ console.log(import_chalk8.default.green("\n\u2713 Authentication complete"));
1451
1474
  console.log(
1452
- import_chalk7.default.dim(
1475
+ import_chalk8.default.dim(
1453
1476
  "\nNext steps:\n 1. Run `commet link` to connect a project\n 2. Run `commet pull` to generate types\n"
1454
1477
  )
1455
1478
  );
@@ -1457,26 +1480,45 @@ var loginCommand = new import_commander6.Command("login").description(
1457
1480
  });
1458
1481
 
1459
1482
  // src/commands/logout.ts
1460
- var import_chalk8 = __toESM(require("chalk"));
1461
- var import_commander7 = require("commander");
1462
- var logoutCommand = new import_commander7.Command("logout").description(
1483
+ var import_chalk9 = __toESM(require("chalk"));
1484
+ var import_commander6 = require("commander");
1485
+ var logoutCommand = new import_commander6.Command("logout").description(
1463
1486
  "Log out and remove stored credentials from ~/.commet/auth.json."
1464
1487
  ).action(async () => {
1488
+ if (process.env.COMMET_API_KEY) {
1489
+ if (isAgentMode()) {
1490
+ console.log(
1491
+ JSON.stringify({
1492
+ success: true,
1493
+ message: "Using COMMET_API_KEY \u2014 no session to clear"
1494
+ })
1495
+ );
1496
+ } else {
1497
+ console.log(
1498
+ import_chalk9.default.green("\u2713 Using COMMET_API_KEY \u2014 no session to clear")
1499
+ );
1500
+ }
1501
+ return;
1502
+ }
1465
1503
  if (!authExists()) {
1466
- console.log(import_chalk8.default.yellow("\u26A0 You are not logged in."));
1504
+ console.log(import_chalk9.default.yellow("\u26A0 You are not logged in."));
1467
1505
  return;
1468
1506
  }
1469
1507
  clearAuth();
1470
- console.log(import_chalk8.default.green("\u2713 Successfully logged out"));
1508
+ console.log(import_chalk9.default.green("\u2713 Successfully logged out"));
1471
1509
  });
1472
1510
 
1473
1511
  // src/commands/orgs.ts
1474
- var import_chalk9 = __toESM(require("chalk"));
1475
- var import_commander8 = require("commander");
1512
+ var import_chalk10 = __toESM(require("chalk"));
1513
+ var import_commander7 = require("commander");
1476
1514
  var import_ora5 = __toESM(require("ora"));
1477
- var orgsCommand = new import_commander8.Command("orgs").description(
1515
+ var orgsCommand = new import_commander7.Command("orgs").description(
1478
1516
  "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(
1517
+ ).option(
1518
+ "--output <format>",
1519
+ "Output format: human (default) or agent",
1520
+ "human"
1521
+ ).addHelpText(
1480
1522
  "after",
1481
1523
  `
1482
1524
  Examples:
@@ -1486,67 +1528,169 @@ Examples:
1486
1528
  The slug shown here is what you pass to 'commet link --org <slug>'.
1487
1529
  `
1488
1530
  ).action(async (options) => {
1489
- const jsonMode = options.json;
1490
- if (!authExists()) {
1491
- if (jsonMode) {
1492
- console.log(JSON.stringify({ error: "Not authenticated" }));
1531
+ const agentMode = isAgentMode(options);
1532
+ requireAuth();
1533
+ const spinner = agentMode ? null : (0, import_ora5.default)("Fetching organizations...").start();
1534
+ const result = await apiRequest(
1535
+ `${BASE_URL}/api/cli/organizations`
1536
+ );
1537
+ if (result.error || !result.data) {
1538
+ if (agentMode) {
1539
+ console.log(JSON.stringify({ error: result.error }));
1493
1540
  } else {
1494
- console.log(import_chalk9.default.red("\u2717 Not authenticated"));
1495
- console.log(import_chalk9.default.dim("Run `commet login` first"));
1541
+ spinner?.fail("Failed to fetch organizations");
1542
+ console.error(import_chalk10.default.red("Error:"), result.error?.message);
1543
+ }
1544
+ process.exit(1);
1545
+ }
1546
+ spinner?.stop();
1547
+ const { organizations } = result.data;
1548
+ if (agentMode) {
1549
+ console.log(JSON.stringify(organizations));
1550
+ return;
1551
+ }
1552
+ if (organizations.length === 0) {
1553
+ console.log(import_chalk10.default.yellow("\u26A0 No organizations found"));
1554
+ console.log(
1555
+ import_chalk10.default.dim("Create an organization at https://commet.co first")
1556
+ );
1557
+ return;
1558
+ }
1559
+ const currentProject = loadProjectConfig();
1560
+ console.log(import_chalk10.default.bold(`
1561
+ Organizations (${organizations.length})
1562
+ `));
1563
+ for (const org of organizations) {
1564
+ const isCurrent = currentProject?.orgId === org.id;
1565
+ const marker = isCurrent ? import_chalk10.default.green("\u25CF") : import_chalk10.default.dim("\u25CB");
1566
+ const mode = org.mode === "live" ? import_chalk10.default.green(org.mode) : import_chalk10.default.yellow(org.mode);
1567
+ console.log(
1568
+ ` ${marker} ${org.name} ${import_chalk10.default.dim(`(${org.slug})`)} ${mode}`
1569
+ );
1570
+ }
1571
+ console.log("");
1572
+ });
1573
+
1574
+ // src/commands/pull.ts
1575
+ var fs5 = __toESM(require("fs"));
1576
+ var path5 = __toESM(require("path"));
1577
+ var import_prompts3 = require("@inquirer/prompts");
1578
+ var import_chalk12 = __toESM(require("chalk"));
1579
+ var import_commander8 = require("commander");
1580
+ var import_ora6 = __toESM(require("ora"));
1581
+
1582
+ // src/utils/config-loader.ts
1583
+ var fs4 = __toESM(require("fs"));
1584
+ var path4 = __toESM(require("path"));
1585
+ var import_jiti = require("jiti");
1586
+ var CONFIG_NAMES = [
1587
+ "commet.config.ts",
1588
+ "commet.config.js",
1589
+ "commet.config.mjs"
1590
+ ];
1591
+ function findConfigFile(cwd) {
1592
+ for (const name of CONFIG_NAMES) {
1593
+ const fullPath = path4.resolve(cwd, name);
1594
+ if (fs4.existsSync(fullPath)) {
1595
+ return fullPath;
1596
+ }
1597
+ }
1598
+ return null;
1599
+ }
1600
+ async function loadBillingConfig(cwd) {
1601
+ const configPath = findConfigFile(cwd);
1602
+ if (!configPath) {
1603
+ throw new Error(
1604
+ `No commet.config.ts found in ${cwd}. Create one with defineConfig() or run 'commet pull' to generate it.`
1605
+ );
1606
+ }
1607
+ const jiti = (0, import_jiti.createJiti)(configPath, { interopDefault: true });
1608
+ const mod = await jiti.import(configPath);
1609
+ if (!mod || typeof mod !== "object") {
1610
+ throw new Error(`${configPath}: failed to load config module`);
1611
+ }
1612
+ const moduleRecord = mod;
1613
+ if (!moduleRecord.default) {
1614
+ throw new Error(
1615
+ `${configPath}: must use \`export default defineConfig({...})\``
1616
+ );
1617
+ }
1618
+ const config = moduleRecord.default;
1619
+ validateConfig(config, configPath);
1620
+ return { config, configPath };
1621
+ }
1622
+ var VALID_FEATURE_TYPES = /* @__PURE__ */ new Set(["boolean", "usage", "seats"]);
1623
+ var VALID_INTERVALS = /* @__PURE__ */ new Set([
1624
+ "weekly",
1625
+ "monthly",
1626
+ "quarterly",
1627
+ "yearly",
1628
+ "one_time"
1629
+ ]);
1630
+ function validateConfig(config, configPath) {
1631
+ if (!config || typeof config !== "object") {
1632
+ throw new Error(`${configPath}: config must be an object`);
1633
+ }
1634
+ if (!config.features || typeof config.features !== "object") {
1635
+ throw new Error(`${configPath}: config.features must be an object`);
1636
+ }
1637
+ if (!config.plans || typeof config.plans !== "object") {
1638
+ throw new Error(`${configPath}: config.plans must be an object`);
1639
+ }
1640
+ for (const [code, feature] of Object.entries(config.features)) {
1641
+ if (!feature.name || typeof feature.name !== "string") {
1642
+ throw new Error(`Feature "${code}": name is required`);
1643
+ }
1644
+ if (!VALID_FEATURE_TYPES.has(feature.type)) {
1645
+ throw new Error(
1646
+ `Feature "${code}": type must be one of: boolean, usage, seats`
1647
+ );
1648
+ }
1649
+ }
1650
+ for (const [code, plan] of Object.entries(config.plans)) {
1651
+ if (!plan.name || typeof plan.name !== "string") {
1652
+ throw new Error(`Plan "${code}": name is required`);
1653
+ }
1654
+ if (!Array.isArray(plan.prices)) {
1655
+ throw new Error(`Plan "${code}": prices must be an array`);
1656
+ }
1657
+ for (const price of plan.prices) {
1658
+ if (!VALID_INTERVALS.has(price.interval)) {
1659
+ throw new Error(
1660
+ `Plan "${code}": price interval "${price.interval}" is not valid`
1661
+ );
1662
+ }
1663
+ if (typeof price.amount !== "number") {
1664
+ throw new Error(`Plan "${code}": price amount must be a number`);
1665
+ }
1666
+ }
1667
+ if (plan.prices.length > 0) {
1668
+ if (!plan.defaultInterval) {
1669
+ throw new Error(
1670
+ `Plan "${code}": defaultInterval is required when prices are defined`
1671
+ );
1672
+ }
1673
+ const priceIntervals = new Set(plan.prices.map((p) => p.interval));
1674
+ if (!priceIntervals.has(plan.defaultInterval)) {
1675
+ throw new Error(
1676
+ `Plan "${code}": defaultInterval "${plan.defaultInterval}" does not match any price interval (${[...priceIntervals].join(", ")})`
1677
+ );
1678
+ }
1496
1679
  }
1497
- process.exit(1);
1498
- }
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);
1680
+ if (plan.features) {
1681
+ for (const featureCode of Object.keys(plan.features)) {
1682
+ if (!config.features[featureCode]) {
1683
+ throw new Error(
1684
+ `Plan "${code}": references feature "${featureCode}" which is not defined in config.features`
1685
+ );
1686
+ }
1687
+ }
1509
1688
  }
1510
- process.exit(1);
1511
- }
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
1689
  }
1537
- console.log("");
1538
- });
1539
-
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"));
1690
+ }
1547
1691
 
1548
1692
  // src/utils/diff.ts
1549
- var import_chalk10 = __toESM(require("chalk"));
1693
+ var import_chalk11 = __toESM(require("chalk"));
1550
1694
  function computeDiff(config, remote) {
1551
1695
  const remoteFeatureMap = new Map(remote.features.map((f) => [f.code, f]));
1552
1696
  const remotePlanMap = new Map(remote.plans.map((p) => [p.code, p]));
@@ -1633,42 +1777,42 @@ function computeDiff(config, remote) {
1633
1777
  }
1634
1778
  function formatDiff(diff) {
1635
1779
  const lines = [];
1636
- lines.push(import_chalk10.default.bold("\nFeatures:"));
1780
+ lines.push(import_chalk11.default.bold("\nFeatures:"));
1637
1781
  for (const change of diff.features.changes) {
1638
1782
  if (change.action === "create") {
1639
- lines.push(import_chalk10.default.green(` + ${change.code}`));
1783
+ lines.push(import_chalk11.default.green(` + ${change.code}`));
1640
1784
  } else if (change.action === "update") {
1641
- lines.push(import_chalk10.default.yellow(` ~ ${change.code}`));
1785
+ lines.push(import_chalk11.default.yellow(` ~ ${change.code}`));
1642
1786
  for (const c of change.changes ?? []) {
1643
- lines.push(import_chalk10.default.dim(` ${c}`));
1787
+ lines.push(import_chalk11.default.dim(` ${c}`));
1644
1788
  }
1645
1789
  } else {
1646
- lines.push(import_chalk10.default.dim(` ${change.code}`));
1790
+ lines.push(import_chalk11.default.dim(` ${change.code}`));
1647
1791
  }
1648
1792
  }
1649
1793
  if (diff.features.unmanaged.length > 0) {
1650
1794
  lines.push(
1651
- import_chalk10.default.dim(
1795
+ import_chalk11.default.dim(
1652
1796
  ` ? unmanaged: ${diff.features.unmanaged.join(", ")} (not in config, left as-is)`
1653
1797
  )
1654
1798
  );
1655
1799
  }
1656
- lines.push(import_chalk10.default.bold("\nPlans:"));
1800
+ lines.push(import_chalk11.default.bold("\nPlans:"));
1657
1801
  for (const change of diff.plans.changes) {
1658
1802
  if (change.action === "create") {
1659
- lines.push(import_chalk10.default.green(` + ${change.code}`));
1803
+ lines.push(import_chalk11.default.green(` + ${change.code}`));
1660
1804
  } else if (change.action === "update") {
1661
- lines.push(import_chalk10.default.yellow(` ~ ${change.code}`));
1805
+ lines.push(import_chalk11.default.yellow(` ~ ${change.code}`));
1662
1806
  for (const c of change.changes ?? []) {
1663
- lines.push(import_chalk10.default.dim(` ${c}`));
1807
+ lines.push(import_chalk11.default.dim(` ${c}`));
1664
1808
  }
1665
1809
  } else {
1666
- lines.push(import_chalk10.default.dim(` ${change.code}`));
1810
+ lines.push(import_chalk11.default.dim(` ${change.code}`));
1667
1811
  }
1668
1812
  }
1669
1813
  if (diff.plans.unmanaged.length > 0) {
1670
1814
  lines.push(
1671
- import_chalk10.default.dim(
1815
+ import_chalk11.default.dim(
1672
1816
  ` ? unmanaged: ${diff.plans.unmanaged.join(", ")} (not in config, left as-is)`
1673
1817
  )
1674
1818
  );
@@ -1752,58 +1896,36 @@ function generateConfigFile(features, plans) {
1752
1896
  }
1753
1897
 
1754
1898
  // src/commands/pull.ts
1755
- var pullCommand = new import_commander9.Command("pull").description(
1899
+ var pullCommand = new import_commander8.Command("pull").description(
1756
1900
  "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(
1901
+ ).option("-y, --yes", "Skip confirmation prompt").option("--dry-run", "Show what would change without writing any files").option(
1902
+ "--output <format>",
1903
+ "Output format: human (default) or agent",
1904
+ "human"
1905
+ ).addHelpText(
1758
1906
  "after",
1759
1907
  `
1760
1908
  Examples:
1761
1909
  $ commet pull Interactive \u2014 shows diff, asks to confirm
1762
1910
  $ commet pull --dry-run Preview changes without applying
1763
1911
  $ commet pull --yes Apply without confirmation
1764
- $ commet pull --json --yes Agent/CI \u2014 structured JSON, no prompts
1912
+ $ commet pull --output agent --yes Agent/CI \u2014 structured JSON, no prompts
1913
+ $ COMMET_API_KEY=sk_... commet pull --yes CI pipeline
1765
1914
  `
1766
1915
  ).action(async (options) => {
1767
- const jsonMode = options.json;
1768
- if (!authExists()) {
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);
1776
- }
1777
- if (!projectConfigExists()) {
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);
1787
- }
1788
- const projectConfig = loadProjectConfig();
1789
- if (!projectConfig) {
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);
1796
- }
1797
- const spinner = jsonMode ? null : (0, import_ora6.default)("Fetching config from remote...").start();
1916
+ const agentMode = isAgentMode(options);
1917
+ const { orgId } = requireOrgContext();
1918
+ const spinner = agentMode ? null : (0, import_ora6.default)("Fetching config from remote...").start();
1919
+ const orgQuery = orgId === "__from_api_key__" ? "" : `?orgId=${orgId}`;
1798
1920
  const result = await apiRequest(
1799
- `${BASE_URL}/api/cli/pull?orgId=${projectConfig.orgId}`
1921
+ `${BASE_URL}/api/cli/pull${orgQuery}`
1800
1922
  );
1801
1923
  if (result.error || !result.data) {
1802
- if (jsonMode) {
1924
+ if (agentMode) {
1803
1925
  console.log(JSON.stringify({ error: result.error }));
1804
1926
  } else {
1805
1927
  spinner?.fail("Failed to fetch config");
1806
- console.error(import_chalk11.default.red("Error:"), result.error);
1928
+ console.error(import_chalk12.default.red("Error:"), result.error?.message);
1807
1929
  }
1808
1930
  process.exit(1);
1809
1931
  }
@@ -1814,7 +1936,7 @@ Examples:
1814
1936
  const existingConfigPath = findConfigFile(process.cwd());
1815
1937
  if (!existingConfigPath) {
1816
1938
  if (options.dryRun) {
1817
- if (jsonMode) {
1939
+ if (agentMode) {
1818
1940
  console.log(
1819
1941
  JSON.stringify({
1820
1942
  action: "create",
@@ -1825,7 +1947,7 @@ Examples:
1825
1947
  );
1826
1948
  } else {
1827
1949
  console.log(
1828
- import_chalk11.default.green(
1950
+ import_chalk12.default.green(
1829
1951
  `
1830
1952
  Would create commet.config.ts (${features.length} features, ${plans.length} plans)`
1831
1953
  )
@@ -1834,7 +1956,7 @@ Would create commet.config.ts (${features.length} features, ${plans.length} plan
1834
1956
  return;
1835
1957
  }
1836
1958
  fs5.writeFileSync(outputPath, configContent, "utf8");
1837
- if (jsonMode) {
1959
+ if (agentMode) {
1838
1960
  console.log(
1839
1961
  JSON.stringify({
1840
1962
  action: "create",
@@ -1844,10 +1966,9 @@ Would create commet.config.ts (${features.length} features, ${plans.length} plan
1844
1966
  })
1845
1967
  );
1846
1968
  } else {
1847
- console.log(import_chalk11.default.green(`
1848
- \u2713 Created commet.config.ts`));
1969
+ console.log(import_chalk12.default.green("\n\u2713 Created commet.config.ts"));
1849
1970
  console.log(
1850
- import_chalk11.default.dim(` ${features.length} features, ${plans.length} plans`)
1971
+ import_chalk12.default.dim(` ${features.length} features, ${plans.length} plans`)
1851
1972
  );
1852
1973
  }
1853
1974
  return;
@@ -1859,7 +1980,7 @@ Would create commet.config.ts (${features.length} features, ${plans.length} plan
1859
1980
  );
1860
1981
  if ("parseError" in localLoaded) {
1861
1982
  if (options.dryRun) {
1862
- if (jsonMode) {
1983
+ if (agentMode) {
1863
1984
  console.log(
1864
1985
  JSON.stringify({
1865
1986
  action: "overwrite",
@@ -1869,7 +1990,7 @@ Would create commet.config.ts (${features.length} features, ${plans.length} plan
1869
1990
  );
1870
1991
  } else {
1871
1992
  console.log(
1872
- import_chalk11.default.yellow(
1993
+ import_chalk12.default.yellow(
1873
1994
  `
1874
1995
  \u26A0 Local config is invalid: ${localLoaded.parseError}`
1875
1996
  )
@@ -1877,23 +1998,23 @@ Would create commet.config.ts (${features.length} features, ${plans.length} plan
1877
1998
  }
1878
1999
  return;
1879
2000
  }
1880
- if (!options.yes && !jsonMode) {
1881
- console.log(import_chalk11.default.yellow(`
2001
+ if (!options.yes && !agentMode) {
2002
+ console.log(import_chalk12.default.yellow(`
1882
2003
  \u26A0 ${localLoaded.parseError}`));
1883
2004
  const shouldProceed = await (0, import_prompts3.confirm)({
1884
2005
  message: "Overwrite with remote?",
1885
2006
  default: true
1886
2007
  });
1887
2008
  if (!shouldProceed) {
1888
- console.log(import_chalk11.default.dim("Pull cancelled"));
2009
+ console.log(import_chalk12.default.dim("Pull cancelled"));
1889
2010
  return;
1890
2011
  }
1891
2012
  }
1892
2013
  fs5.writeFileSync(outputPath, configContent, "utf8");
1893
- if (jsonMode) {
2014
+ if (agentMode) {
1894
2015
  console.log(JSON.stringify({ action: "overwrite", applied: true }));
1895
2016
  } else {
1896
- console.log(import_chalk11.default.green("\n\u2713 Overwritten commet.config.ts"));
2017
+ console.log(import_chalk12.default.green("\n\u2713 Overwritten commet.config.ts"));
1897
2018
  }
1898
2019
  return;
1899
2020
  }
@@ -1916,9 +2037,7 @@ Would create commet.config.ts (${features.length} features, ${plans.length} plan
1916
2037
  {
1917
2038
  name: p.name,
1918
2039
  ...p.description ? { description: p.description } : {},
1919
- ...p.consumptionModel ? {
1920
- consumptionModel: p.consumptionModel
1921
- } : {},
2040
+ ...p.consumptionModel ? { consumptionModel: p.consumptionModel } : {},
1922
2041
  ...p.isFree ? { isFree: true } : {},
1923
2042
  ...p.isPublic === false ? { isPublic: false } : {},
1924
2043
  ...p.sortOrder ? { sortOrder: p.sortOrder } : {},
@@ -1964,14 +2083,14 @@ Would create commet.config.ts (${features.length} features, ${plans.length} plan
1964
2083
  };
1965
2084
  const diff = computeDiff(remoteAsConfig, localAsRemote);
1966
2085
  if (!diff.hasChanges && diff.features.unmanaged.length === 0 && diff.plans.unmanaged.length === 0) {
1967
- if (jsonMode) {
2086
+ if (agentMode) {
1968
2087
  console.log(JSON.stringify({ diff, applied: false, upToDate: true }));
1969
2088
  } else {
1970
- console.log(import_chalk11.default.green("\n\u2713 Already up to date"));
2089
+ console.log(import_chalk12.default.green("\n\u2713 Already up to date"));
1971
2090
  }
1972
2091
  return;
1973
2092
  }
1974
- if (jsonMode) {
2093
+ if (agentMode) {
1975
2094
  if (options.dryRun) {
1976
2095
  console.log(JSON.stringify({ diff, applied: false }));
1977
2096
  return;
@@ -1980,92 +2099,69 @@ Would create commet.config.ts (${features.length} features, ${plans.length} plan
1980
2099
  console.log(formatDiff(diff));
1981
2100
  }
1982
2101
  if (options.dryRun) {
1983
- if (!jsonMode) {
1984
- console.log(import_chalk11.default.dim("\n(dry run \u2014 no changes applied)"));
2102
+ if (!agentMode) {
2103
+ console.log(import_chalk12.default.dim("\n(dry run \u2014 no changes applied)"));
1985
2104
  }
1986
2105
  return;
1987
2106
  }
1988
- if (!options.yes && !jsonMode) {
2107
+ if (!options.yes && !agentMode) {
1989
2108
  const shouldProceed = await (0, import_prompts3.confirm)({
1990
2109
  message: "Overwrite commet.config.ts with remote state?",
1991
2110
  default: true
1992
2111
  });
1993
2112
  if (!shouldProceed) {
1994
- console.log(import_chalk11.default.dim("Pull cancelled"));
2113
+ console.log(import_chalk12.default.dim("Pull cancelled"));
1995
2114
  return;
1996
2115
  }
1997
2116
  }
1998
2117
  fs5.writeFileSync(outputPath, configContent, "utf8");
1999
- if (jsonMode) {
2118
+ if (agentMode) {
2000
2119
  console.log(JSON.stringify({ diff, applied: true }));
2001
2120
  } else {
2002
- console.log(import_chalk11.default.green("\n\u2713 Updated commet.config.ts"));
2121
+ console.log(import_chalk12.default.green("\n\u2713 Updated commet.config.ts"));
2003
2122
  console.log(
2004
- import_chalk11.default.dim(` ${features.length} features, ${plans.length} plans`)
2123
+ import_chalk12.default.dim(` ${features.length} features, ${plans.length} plans`)
2005
2124
  );
2006
2125
  }
2007
2126
  });
2008
2127
 
2009
2128
  // src/commands/push.ts
2010
2129
  var import_prompts4 = require("@inquirer/prompts");
2011
- var import_chalk12 = __toESM(require("chalk"));
2012
- var import_commander10 = require("commander");
2130
+ var import_chalk13 = __toESM(require("chalk"));
2131
+ var import_commander9 = require("commander");
2013
2132
  var import_ora7 = __toESM(require("ora"));
2014
- var pushCommand = new import_commander10.Command("push").description(
2133
+ var pushCommand = new import_commander9.Command("push").description(
2015
2134
  "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(
2135
+ ).option("-y, --yes", "Skip confirmation prompt").option("--dry-run", "Show what would change without pushing").option(
2136
+ "--output <format>",
2137
+ "Output format: human (default) or agent",
2138
+ "human"
2139
+ ).addHelpText(
2017
2140
  "after",
2018
2141
  `
2019
2142
  Examples:
2020
2143
  $ commet push Interactive \u2014 shows diff, asks to confirm
2021
2144
  $ commet push --dry-run Preview what would change on remote
2022
2145
  $ 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).
2146
+ $ commet push --output agent --yes Agent/CI \u2014 structured JSON, no prompts
2147
+ $ COMMET_API_KEY=sk_... commet push --yes CI pipeline
2028
2148
  `
2029
2149
  ).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);
2039
- }
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();
2150
+ const agentMode = isAgentMode(options);
2151
+ const { orgId } = requireOrgContext();
2152
+ const loadSpinner = agentMode ? null : (0, import_ora7.default)("Loading commet.config.ts...").start();
2061
2153
  const loaded = await loadBillingConfig(process.cwd()).catch(
2062
2154
  (error) => {
2063
2155
  const message = error instanceof Error ? error.message : String(error);
2064
- if (jsonMode) {
2065
- console.log(JSON.stringify({ error: message }));
2156
+ if (agentMode) {
2157
+ console.log(
2158
+ JSON.stringify({
2159
+ error: { code: "config_invalid", message }
2160
+ })
2161
+ );
2066
2162
  } else {
2067
2163
  loadSpinner?.fail("Failed to load config");
2068
- console.error(import_chalk12.default.red(message));
2164
+ console.error(import_chalk13.default.red(message));
2069
2165
  }
2070
2166
  return null;
2071
2167
  }
@@ -2073,16 +2169,17 @@ Notes:
2073
2169
  if (!loaded) process.exit(1);
2074
2170
  const { config, configPath } = loaded;
2075
2171
  loadSpinner?.succeed(`Loaded ${configPath}`);
2076
- const fetchSpinner = jsonMode ? null : (0, import_ora7.default)("Fetching remote state...").start();
2172
+ const fetchSpinner = agentMode ? null : (0, import_ora7.default)("Fetching remote state...").start();
2173
+ const orgQuery = orgId === "__from_api_key__" ? "" : `?orgId=${orgId}`;
2077
2174
  const remoteResult = await apiRequest(
2078
- `${BASE_URL}/api/cli/pull?orgId=${projectConfig.orgId}`
2175
+ `${BASE_URL}/api/cli/pull${orgQuery}`
2079
2176
  );
2080
2177
  if (remoteResult.error || !remoteResult.data) {
2081
- if (jsonMode) {
2178
+ if (agentMode) {
2082
2179
  console.log(JSON.stringify({ error: remoteResult.error }));
2083
2180
  } else {
2084
2181
  fetchSpinner?.fail("Failed to fetch remote state");
2085
- console.error(import_chalk12.default.red("Error:"), remoteResult.error);
2182
+ console.error(import_chalk13.default.red("Error:"), remoteResult.error?.message);
2086
2183
  }
2087
2184
  process.exit(1);
2088
2185
  }
@@ -2092,7 +2189,7 @@ Notes:
2092
2189
  plans: remoteResult.data.plans
2093
2190
  };
2094
2191
  const diff = computeDiff(config, remote);
2095
- if (jsonMode) {
2192
+ if (agentMode) {
2096
2193
  if (options.dryRun) {
2097
2194
  console.log(JSON.stringify({ diff, applied: false }));
2098
2195
  return;
@@ -2101,10 +2198,10 @@ Notes:
2101
2198
  console.log(formatDiff(diff));
2102
2199
  }
2103
2200
  if (!diff.hasChanges) {
2104
- if (jsonMode) {
2201
+ if (agentMode) {
2105
2202
  console.log(JSON.stringify({ diff, applied: false, upToDate: true }));
2106
2203
  } else {
2107
- console.log(import_chalk12.default.green("\n\u2713 Everything is up to date"));
2204
+ console.log(import_chalk13.default.green("\n\u2713 Everything is up to date"));
2108
2205
  }
2109
2206
  return;
2110
2207
  }
@@ -2113,67 +2210,67 @@ Notes:
2113
2210
  );
2114
2211
  if (typeChanges.length > 0) {
2115
2212
  const blockedCodes = typeChanges.map((c) => c.code);
2116
- if (jsonMode) {
2213
+ if (agentMode) {
2117
2214
  console.log(
2118
2215
  JSON.stringify({
2119
- error: "Feature type changes blocked",
2216
+ error: {
2217
+ code: "type_change_blocked",
2218
+ message: "Feature type changes must be done in the dashboard"
2219
+ },
2120
2220
  blockedCodes,
2121
2221
  diff
2122
2222
  })
2123
2223
  );
2124
2224
  } else {
2125
2225
  console.log(
2126
- import_chalk12.default.red(
2226
+ import_chalk13.default.red(
2127
2227
  "\n\u2717 Cannot change feature types. Update them in the dashboard:"
2128
2228
  )
2129
2229
  );
2130
2230
  for (const change of typeChanges) {
2131
- console.log(import_chalk12.default.red(` - ${change.code}`));
2231
+ console.log(import_chalk13.default.red(` - ${change.code}`));
2132
2232
  }
2133
2233
  }
2134
2234
  process.exit(1);
2135
2235
  }
2136
2236
  if (options.dryRun) {
2137
- if (!jsonMode) {
2138
- console.log(import_chalk12.default.dim("\n(dry run \u2014 no changes applied)"));
2237
+ if (!agentMode) {
2238
+ console.log(import_chalk13.default.dim("\n(dry run \u2014 no changes applied)"));
2139
2239
  }
2140
2240
  return;
2141
2241
  }
2142
- if (!options.yes && !jsonMode) {
2242
+ if (!options.yes && !agentMode) {
2143
2243
  const shouldProceed = await (0, import_prompts4.confirm)({
2144
2244
  message: "Apply these changes?",
2145
2245
  default: true
2146
2246
  });
2147
2247
  if (!shouldProceed) {
2148
- console.log(import_chalk12.default.dim("Push cancelled"));
2248
+ console.log(import_chalk13.default.dim("Push cancelled"));
2149
2249
  return;
2150
2250
  }
2151
2251
  }
2152
- const pushSpinner = jsonMode ? null : (0, import_ora7.default)("Pushing config...").start();
2252
+ const pushSpinner = agentMode ? null : (0, import_ora7.default)("Pushing config...").start();
2253
+ const pushBody = {
2254
+ config: { features: config.features, plans: config.plans }
2255
+ };
2256
+ if (orgId !== "__from_api_key__") {
2257
+ pushBody.orgId = orgId;
2258
+ }
2153
2259
  const pushResult = await apiRequest(
2154
2260
  `${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
- }
2261
+ { method: "POST", body: JSON.stringify(pushBody) }
2165
2262
  );
2166
2263
  if (pushResult.error || !pushResult.data) {
2167
- if (jsonMode) {
2264
+ if (agentMode) {
2168
2265
  console.log(JSON.stringify({ error: pushResult.error }));
2169
2266
  } else {
2170
2267
  pushSpinner?.fail("Push failed");
2171
- console.error(import_chalk12.default.red("Error:"), pushResult.error);
2268
+ console.error(import_chalk13.default.red("Error:"), pushResult.error?.message);
2172
2269
  }
2173
2270
  process.exit(1);
2174
2271
  }
2175
2272
  const pushOutcome = pushResult.data;
2176
- if (jsonMode) {
2273
+ if (agentMode) {
2177
2274
  console.log(JSON.stringify({ diff, applied: true, result: pushOutcome }));
2178
2275
  return;
2179
2276
  }
@@ -2184,160 +2281,41 @@ Notes:
2184
2281
  if (errors.length > 0) {
2185
2282
  pushSpinner?.warn("Push completed with errors");
2186
2283
  for (const error of errors) {
2187
- console.log(import_chalk12.default.red(` \u2717 ${error.code}: ${error.message}`));
2284
+ console.log(import_chalk13.default.red(` \u2717 ${error.code}: ${error.message}`));
2188
2285
  }
2189
2286
  } else {
2190
2287
  pushSpinner?.succeed("Push complete");
2191
2288
  }
2192
2289
  if (pushOutcome.features.created.length > 0) {
2193
2290
  console.log(
2194
- import_chalk12.default.green(
2291
+ import_chalk13.default.green(
2195
2292
  ` Created features: ${pushOutcome.features.created.join(", ")}`
2196
2293
  )
2197
2294
  );
2198
2295
  }
2199
2296
  if (pushOutcome.features.updated.length > 0) {
2200
2297
  console.log(
2201
- import_chalk12.default.yellow(
2298
+ import_chalk13.default.yellow(
2202
2299
  ` Updated features: ${pushOutcome.features.updated.join(", ")}`
2203
2300
  )
2204
2301
  );
2205
2302
  }
2206
2303
  if (pushOutcome.plans.created.length > 0) {
2207
2304
  console.log(
2208
- import_chalk12.default.green(` Created plans: ${pushOutcome.plans.created.join(", ")}`)
2305
+ import_chalk13.default.green(` Created plans: ${pushOutcome.plans.created.join(", ")}`)
2209
2306
  );
2210
2307
  }
2211
2308
  if (pushOutcome.plans.updated.length > 0) {
2212
2309
  console.log(
2213
- import_chalk12.default.yellow(
2310
+ import_chalk13.default.yellow(
2214
2311
  ` Updated plans: ${pushOutcome.plans.updated.join(", ")}`
2215
2312
  )
2216
2313
  );
2217
2314
  }
2218
2315
  });
2219
2316
 
2220
- // src/commands/switch.ts
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) => {
2241
- if (!authExists()) {
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);
2245
- }
2246
- if (!projectConfigExists()) {
2247
- console.log(import_chalk13.default.yellow("\u26A0 Project not linked"));
2248
- console.log(
2249
- import_chalk13.default.dim("Run `commet link` first to connect to an organization")
2250
- );
2251
- process.exit(1);
2252
- }
2253
- const spinner = (0, import_ora8.default)("Fetching organizations...").start();
2254
- const result = await apiRequest(
2255
- `${BASE_URL}/api/cli/organizations`
2256
- );
2257
- if (result.error || !result.data) {
2258
- spinner.fail("Failed to fetch organizations");
2259
- console.error(import_chalk13.default.red("Error:"), result.error);
2260
- process.exit(1);
2261
- }
2262
- const { organizations } = result.data;
2263
- if (organizations.length === 0) {
2264
- spinner.stop();
2265
- console.log(import_chalk13.default.yellow("\u26A0 No organizations found"));
2266
- process.exit(1);
2267
- }
2268
- spinner.stop();
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
- }
2302
- }
2303
- saveProjectConfig({
2304
- orgId: selectedOrg.id,
2305
- orgName: selectedOrg.name,
2306
- mode: selectedOrg.mode
2307
- });
2308
- console.log(import_chalk13.default.green("\n\u2713 Switched organization"));
2309
- console.log(
2310
- import_chalk13.default.dim(`Organization: ${selectedOrg.name} \xB7 ${selectedOrg.mode}`)
2311
- );
2312
- console.log(
2313
- import_chalk13.default.dim(
2314
- "\nRun `commet pull` to update TypeScript types for this organization"
2315
- )
2316
- );
2317
- });
2318
-
2319
- // src/commands/unlink.ts
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 () => {
2325
- if (!projectConfigExists()) {
2326
- console.log(
2327
- import_chalk14.default.yellow("\u26A0 This project is not linked to any organization")
2328
- );
2329
- return;
2330
- }
2331
- clearProjectConfig();
2332
- console.log(import_chalk14.default.green("\u2713 Project unlinked successfully"));
2333
- console.log(import_chalk14.default.dim("\u2713 Removed .commet/ directory"));
2334
- console.log(
2335
- import_chalk14.default.dim("\nRun `commet link` to connect to a different organization")
2336
- );
2337
- });
2338
-
2339
2317
  // src/index.ts
2340
- var program = new import_commander13.Command();
2318
+ var program = new import_commander10.Command();
2341
2319
  program.name("commet").description(
2342
2320
  "Commet CLI \u2014 billing infrastructure as code.\nManage features, plans, and billing config from the command line."
2343
2321
  ).version(package_default.version).addHelpText(
@@ -2346,13 +2324,12 @@ program.name("commet").description(
2346
2324
  Workflow:
2347
2325
  $ commet pull Sync remote \u2192 commet.config.ts
2348
2326
  $ commet push Push local changes \u2192 remote
2349
- $ commet list features See what's configured
2327
+ $ commet pull --dry-run See what's configured
2350
2328
 
2351
2329
  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
2330
+ $ commet JSON capabilities when piped (no args)
2331
+ $ commet pull --output agent --yes Structured output, no prompts
2332
+ $ COMMET_API_KEY=sk_... commet push --yes CI pipeline
2356
2333
 
2357
2334
  Run commet <command> --help for detailed usage and examples.
2358
2335
  `
@@ -2361,16 +2338,31 @@ program.addCommand(createCommand);
2361
2338
  program.addCommand(loginCommand);
2362
2339
  program.addCommand(logoutCommand);
2363
2340
  program.addCommand(linkCommand);
2364
- program.addCommand(unlinkCommand);
2365
- program.addCommand(switchCommand);
2366
- program.addCommand(agentInfoCommand);
2367
2341
  program.addCommand(orgsCommand);
2368
2342
  program.addCommand(pushCommand);
2369
2343
  program.addCommand(pullCommand);
2370
- program.addCommand(listCommand);
2371
2344
  program.addCommand(listenCommand);
2345
+ program.addCommand(apiKeyCommand);
2346
+ program.enablePositionalOptions().passThroughOptions().option(
2347
+ "--output <format>",
2348
+ "Output format: human (default) or agent",
2349
+ "human"
2350
+ );
2351
+ program.action((options) => {
2352
+ if (options.output === "agent") {
2353
+ printAgentInfo();
2354
+ } else {
2355
+ printDefaultScreen();
2356
+ }
2357
+ });
2372
2358
  program.showSuggestionAfterError();
2373
2359
  program.exitOverride();
2360
+ installCrashHandler();
2361
+ markCommandStart();
2362
+ var commandName = process.argv[2] || "(default)";
2363
+ program.hook("postAction", () => {
2364
+ reportCommand(commandName, "success");
2365
+ });
2374
2366
  try {
2375
2367
  program.parse(process.argv);
2376
2368
  } catch (error) {
@@ -2379,69 +2371,145 @@ try {
2379
2371
  if (code === "commander.version" || code === "commander.help" || code === "commander.helpDisplayed") {
2380
2372
  process.exit(0);
2381
2373
  }
2382
- console.error(import_chalk15.default.red("Error:"), error.message);
2374
+ reportCommand(commandName, "error", code);
2375
+ console.error(import_chalk14.default.red("Error:"), error.message);
2383
2376
  }
2384
2377
  process.exit(1);
2385
2378
  }
2386
- if (!process.argv.slice(2).length) {
2387
- printDefaultScreen();
2379
+ function printAgentInfo() {
2380
+ const authenticated = authExists() || !!process.env.COMMET_API_KEY;
2381
+ const projectConfig = projectConfigExists() ? loadProjectConfig() : null;
2382
+ const configPath = findConfigFile(process.cwd());
2383
+ const setup = [];
2384
+ if (!authenticated) {
2385
+ setup.push(
2386
+ "Not authenticated. Run 'commet login' (interactive) or set COMMET_API_KEY env var."
2387
+ );
2388
+ }
2389
+ if (authenticated && !projectConfig && !process.env.COMMET_API_KEY) {
2390
+ setup.push(
2391
+ "No project linked. Run 'commet link --org <slug>' or 'commet orgs --json' to find organizations."
2392
+ );
2393
+ }
2394
+ const output = {
2395
+ version: package_default.version,
2396
+ authenticated,
2397
+ ...setup.length > 0 ? { setup } : {},
2398
+ project: projectConfig ? {
2399
+ linked: true,
2400
+ orgId: projectConfig.orgId,
2401
+ orgName: projectConfig.orgName,
2402
+ mode: projectConfig.mode
2403
+ } : { linked: false },
2404
+ config: {
2405
+ exists: configPath !== null,
2406
+ path: configPath?.split("/").pop() ?? null
2407
+ },
2408
+ mcp: {
2409
+ url: "https://commet.co/mcp",
2410
+ sandbox: "https://sandbox.commet.co/mcp",
2411
+ hint: "For full billing CRUD (plans, features, customers, subscriptions), connect to the MCP server."
2412
+ },
2413
+ auth: {
2414
+ interactive: "commet login",
2415
+ ci: "Set COMMET_API_KEY environment variable"
2416
+ },
2417
+ commands: {
2418
+ pull: {
2419
+ description: "Pull remote config and generate commet.config.ts",
2420
+ usage: "commet pull --output agent --yes",
2421
+ preview: "commet pull --output agent --dry-run"
2422
+ },
2423
+ push: {
2424
+ description: "Push commet.config.ts to remote",
2425
+ usage: "commet push --output agent --yes",
2426
+ preview: "commet push --output agent --dry-run",
2427
+ ci: "COMMET_API_KEY=sk_... commet push --yes"
2428
+ },
2429
+ orgs: {
2430
+ description: "List available organizations",
2431
+ usage: "commet orgs --output agent"
2432
+ },
2433
+ link: {
2434
+ description: "Link/switch/unlink organization",
2435
+ usage: "commet link --org <slug-or-id>",
2436
+ clear: "commet link --clear"
2437
+ },
2438
+ listen: {
2439
+ description: "Forward webhook events to local server. Long-running streaming process.",
2440
+ usage: "commet listen <url> [--events <types>]"
2441
+ },
2442
+ create: {
2443
+ description: "Scaffold a new Commet app from template",
2444
+ usage: "commet create [name] -t <template> --org <slug> -y"
2445
+ },
2446
+ "api-key": {
2447
+ description: "Generate API key for CI",
2448
+ usage: "commet api-key --output agent"
2449
+ },
2450
+ login: {
2451
+ description: "Authenticate via browser. Requires a human \u2014 opens a device-code flow.",
2452
+ usage: "commet login"
2453
+ },
2454
+ logout: {
2455
+ description: "Log out of Commet",
2456
+ usage: "commet logout"
2457
+ }
2458
+ }
2459
+ };
2460
+ console.log(JSON.stringify(output, null, 2));
2388
2461
  }
2389
2462
  function printDefaultScreen() {
2390
- const version = import_chalk15.default.dim(`v${package_default.version}`);
2463
+ const version = import_chalk14.default.dim(`v${package_default.version}`);
2391
2464
  console.log(`
2392
2465
  ${commetColor.bold("Commet")} ${version}`);
2393
- console.log(import_chalk15.default.dim(" Billing infrastructure as code\n"));
2466
+ console.log(import_chalk14.default.dim(" Billing infrastructure as code\n"));
2394
2467
  const authenticated = authExists();
2395
2468
  const projectConfig = projectConfigExists() ? loadProjectConfig() : null;
2396
2469
  const configFile = findConfigFile(process.cwd());
2397
2470
  if (authenticated) {
2398
- console.log(import_chalk15.default.green(" \u2713 Authenticated"));
2471
+ console.log(import_chalk14.default.green(" \u2713 Authenticated"));
2399
2472
  } else {
2400
2473
  console.log(
2401
- ` ${import_chalk15.default.yellow("\u26A0")} Not logged in ${import_chalk15.default.dim("\u2192 commet login")}`
2474
+ ` ${import_chalk14.default.yellow("\u26A0")} Not logged in ${import_chalk14.default.dim("\u2192 commet login")}`
2402
2475
  );
2403
2476
  }
2404
2477
  if (projectConfig) {
2405
2478
  console.log(
2406
- import_chalk15.default.green(
2407
- ` \u2713 ${projectConfig.orgName} ${import_chalk15.default.dim(`(${projectConfig.mode})`)}`
2479
+ import_chalk14.default.green(
2480
+ ` \u2713 ${projectConfig.orgName} ${import_chalk14.default.dim(`(${projectConfig.mode})`)}`
2408
2481
  )
2409
2482
  );
2410
- console.log(import_chalk15.default.dim(` ${projectConfig.orgId}`));
2483
+ console.log(import_chalk14.default.dim(` ${projectConfig.orgId}`));
2411
2484
  } else if (authenticated) {
2412
2485
  console.log(
2413
- ` ${import_chalk15.default.yellow("\u26A0")} No project linked ${import_chalk15.default.dim("\u2192 commet link")}`
2486
+ ` ${import_chalk14.default.yellow("\u26A0")} No project linked ${import_chalk14.default.dim("\u2192 commet link")}`
2414
2487
  );
2415
2488
  }
2416
2489
  if (configFile) {
2417
2490
  const fileName = configFile.split("/").pop();
2418
- console.log(import_chalk15.default.green(` \u2713 ${fileName}`));
2491
+ console.log(import_chalk14.default.green(` \u2713 ${fileName}`));
2419
2492
  } else if (projectConfig) {
2420
2493
  console.log(
2421
- ` ${import_chalk15.default.yellow("\u26A0")} No config file ${import_chalk15.default.dim("\u2192 commet pull")}`
2494
+ ` ${import_chalk14.default.yellow("\u26A0")} No config file ${import_chalk14.default.dim("\u2192 commet pull")}`
2422
2495
  );
2423
2496
  }
2424
2497
  const cmd = (name) => commetColor(name.padEnd(22));
2425
- const dim = import_chalk15.default.dim;
2498
+ const dim = import_chalk14.default.dim;
2426
2499
  console.log(dim("\n Config"));
2427
2500
  console.log(` ${cmd("pull")}${dim("Sync remote \u2192 commet.config.ts")}`);
2428
2501
  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
2502
  console.log(dim("\n Development"));
2433
2503
  console.log(` ${cmd("listen <url>")}${dim("Forward webhooks locally")}`);
2434
2504
  console.log(dim("\n Project"));
2435
- console.log(` ${cmd("link")}${dim("Link to an organization")}`);
2436
- console.log(` ${cmd("switch")}${dim("Switch organization")}`);
2505
+ console.log(` ${cmd("link")}${dim("Link / switch organization")}`);
2437
2506
  console.log(` ${cmd("orgs")}${dim("List organizations")}`);
2438
2507
  console.log(dim("\n Setup"));
2439
2508
  console.log(` ${cmd("create")}${dim("Scaffold a new Commet app")}`);
2509
+ console.log(` ${cmd("api-key")}${dim("Generate API key for CI")}`);
2440
2510
  console.log(` ${cmd("login")}${dim("Authenticate")}`);
2441
2511
  console.log(` ${cmd("logout")}${dim("Log out")}`);
2442
2512
  console.log(
2443
- dim(
2444
- "\n commet --help for full reference \xB7 commet agent-info for agents/CI\n"
2445
- )
2513
+ dim("\n commet --help for full reference \xB7 COMMET_API_KEY for CI\n")
2446
2514
  );
2447
2515
  }