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