@cometchat/skills-cli 2.0.0 → 2.0.1

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 CHANGED
@@ -1350,6 +1350,128 @@ function checkOverwriteSafetyBatch(root, framework, filePaths) {
1350
1350
  return filePaths.map((p2) => checkOverwriteSafety(root, framework, p2));
1351
1351
  }
1352
1352
 
1353
+ // src/utils/env-file.ts
1354
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
1355
+ import { join as join7 } from "node:path";
1356
+ var PLACEHOLDER_SENTINEL = "YOUR_";
1357
+ var PLACEHOLDER_SUFFIX = "_HERE";
1358
+ function envFileNameFor(framework) {
1359
+ return framework === "nextjs" ? ".env.local" : ".env";
1360
+ }
1361
+ function placeholderFor(varName) {
1362
+ let stripped = varName.replace(/^[A-Z]+_/, "");
1363
+ stripped = stripped.replace(/^COMETCHAT_/, "");
1364
+ return `${PLACEHOLDER_SENTINEL}${stripped}${PLACEHOLDER_SUFFIX}`;
1365
+ }
1366
+ function isPlaceholder(value) {
1367
+ const trimmed = value.trim();
1368
+ return trimmed.startsWith(PLACEHOLDER_SENTINEL) && trimmed.endsWith(PLACEHOLDER_SUFFIX);
1369
+ }
1370
+ function parseEnvKeys(content) {
1371
+ const keys = /* @__PURE__ */ new Set();
1372
+ for (const line of content.split(/\r?\n/)) {
1373
+ const trimmed = line.trim();
1374
+ if (!trimmed || trimmed.startsWith("#")) continue;
1375
+ const eq = trimmed.indexOf("=");
1376
+ if (eq === -1) continue;
1377
+ const key = trimmed.slice(0, eq).trim();
1378
+ if (key) keys.add(key);
1379
+ }
1380
+ return keys;
1381
+ }
1382
+ function parseEnvPairs(content) {
1383
+ const pairs = /* @__PURE__ */ new Map();
1384
+ for (const line of content.split(/\r?\n/)) {
1385
+ const trimmed = line.trim();
1386
+ if (!trimmed || trimmed.startsWith("#")) continue;
1387
+ const eq = trimmed.indexOf("=");
1388
+ if (eq === -1) continue;
1389
+ const key = trimmed.slice(0, eq).trim();
1390
+ let value = trimmed.slice(eq + 1).trim();
1391
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1392
+ value = value.slice(1, -1);
1393
+ }
1394
+ const commentIdx = value.indexOf(" #");
1395
+ if (commentIdx !== -1) {
1396
+ value = value.slice(0, commentIdx).trim();
1397
+ }
1398
+ if (key) pairs.set(key, value);
1399
+ }
1400
+ return pairs;
1401
+ }
1402
+ function writeEnvFile(args) {
1403
+ const { projectRoot, framework, envVars } = args;
1404
+ const fileName = envFileNameFor(framework);
1405
+ const fullPath = join7(projectRoot, fileName);
1406
+ let existing = "";
1407
+ if (pathExists(fullPath)) {
1408
+ try {
1409
+ existing = readFileSync7(fullPath, "utf8");
1410
+ } catch {
1411
+ existing = "";
1412
+ }
1413
+ }
1414
+ const presentKeys = parseEnvKeys(existing);
1415
+ const toWrite = [];
1416
+ const skipped = [];
1417
+ for (const v of envVars) {
1418
+ if (presentKeys.has(v.name)) {
1419
+ skipped.push(v.name);
1420
+ continue;
1421
+ }
1422
+ toWrite.push({ name: v.name, placeholder: placeholderFor(v.name) });
1423
+ }
1424
+ if (toWrite.length > 0) {
1425
+ const lines = [];
1426
+ if (existing.length > 0 && !existing.endsWith("\n")) lines.push("");
1427
+ if (existing.length > 0) lines.push("");
1428
+ lines.push("# CometChat \u2014 fill in real values from https://app.cometchat.com \u2192 Your App \u2192 API & Auth Keys");
1429
+ for (const v of envVars) {
1430
+ if (presentKeys.has(v.name)) continue;
1431
+ const placeholder = placeholderFor(v.name);
1432
+ const inlineComment = v.secret ? " # secret \u2014 do not commit" : v.description ? ` # ${v.description}` : "";
1433
+ lines.push(`${v.name}=${placeholder}${inlineComment}`);
1434
+ }
1435
+ lines.push("");
1436
+ writeFileSync4(fullPath, existing + lines.join("\n"), "utf8");
1437
+ }
1438
+ const gitignoreUpdated = ensureEnvIgnored(projectRoot, fileName);
1439
+ return {
1440
+ path: fileName,
1441
+ written: toWrite,
1442
+ skipped,
1443
+ gitignore_updated: gitignoreUpdated
1444
+ };
1445
+ }
1446
+ function ensureEnvIgnored(projectRoot, envFileName) {
1447
+ const gitignorePath = join7(projectRoot, ".gitignore");
1448
+ let existing = "";
1449
+ if (pathExists(gitignorePath)) {
1450
+ try {
1451
+ existing = readFileSync7(gitignorePath, "utf8");
1452
+ } catch {
1453
+ existing = "";
1454
+ }
1455
+ }
1456
+ const lines = existing.split(/\r?\n/).map((l) => l.trim());
1457
+ for (const line of lines) {
1458
+ if (!line || line.startsWith("#")) continue;
1459
+ if (line === envFileName) return false;
1460
+ if (line === `/${envFileName}`) return false;
1461
+ if (line === ".env*" || line === "/.env*") return false;
1462
+ if (line === ".env*.local" && envFileName.endsWith(".local")) return false;
1463
+ if (line === ".env" && envFileName === ".env") return false;
1464
+ }
1465
+ const block = [];
1466
+ if (existing.length > 0 && !existing.endsWith("\n")) block.push("");
1467
+ if (existing.length > 0) block.push("");
1468
+ block.push("# CometChat \u2014 keep credentials out of source control");
1469
+ block.push(envFileName);
1470
+ block.push("");
1471
+ writeFileSync4(gitignorePath, existing + block.join("\n"), "utf8");
1472
+ return true;
1473
+ }
1474
+
1353
1475
  // src/commands/apply.ts
1354
1476
  function isJsonMode4(args) {
1355
1477
  return args.flags.json === true || args.flags.json === "true";
@@ -1714,6 +1836,18 @@ async function apply(args) {
1714
1836
  state_file: ".cometchat/state.json",
1715
1837
  next_steps: (manifest.next_steps ?? []).map((s) => substitute(s, subs))
1716
1838
  };
1839
+ if (manifest.env_vars.length > 0) {
1840
+ result.env_file = writeEnvFile({
1841
+ projectRoot: root,
1842
+ framework,
1843
+ envVars: result.env_vars_to_add.map((v) => ({
1844
+ name: v.name,
1845
+ required: v.required,
1846
+ secret: v.secret,
1847
+ description: manifest.env_vars.find((e) => substitute(e.name, subs) === v.name)?.description
1848
+ }))
1849
+ });
1850
+ }
1717
1851
  if (embedPath !== null && entryPath !== null) {
1718
1852
  const embedHints = embedNextSteps(framework, embedPath);
1719
1853
  result.next_steps = [...embedHints, ...result.next_steps];
@@ -1840,12 +1974,24 @@ function printHumanReadable3(r) {
1840
1974
  lines.push(" (or pass --auto-install next time to do this automatically)");
1841
1975
  }
1842
1976
  }
1843
- if (r.env_vars_to_add.length > 0) {
1977
+ if (r.env_file) {
1844
1978
  lines.push("");
1845
- lines.push(" Add these to your .env file:");
1846
- for (const e of r.env_vars_to_add) {
1847
- const tag = e.secret ? " # secret \u2014 do not commit" : "";
1848
- lines.push(` ${e.name}=YOUR_VALUE_HERE${tag}`);
1979
+ if (r.env_file.written.length > 0) {
1980
+ lines.push(
1981
+ ` \u2713 Wrote ${r.env_file.written.length} placeholder env var(s) to ${r.env_file.path}`
1982
+ );
1983
+ lines.push(` Open ${r.env_file.path} and replace the YOUR_*_HERE values:`);
1984
+ for (const v of r.env_file.written) {
1985
+ lines.push(` ${v.name}=${v.placeholder}`);
1986
+ }
1987
+ lines.push(` (get real values from https://app.cometchat.com \u2192 Your App \u2192 API & Auth Keys)`);
1988
+ } else if (r.env_vars_to_add.length > 0) {
1989
+ lines.push(
1990
+ ` \u2713 All ${r.env_vars_to_add.length} env var(s) already present in ${r.env_file.path}`
1991
+ );
1992
+ }
1993
+ if (r.env_file.gitignore_updated) {
1994
+ lines.push(` + Added ${r.env_file.path} to .gitignore`);
1849
1995
  }
1850
1996
  }
1851
1997
  lines.push("");
@@ -2127,7 +2273,7 @@ function printHumanReadable5(r) {
2127
2273
 
2128
2274
  // src/commands/uninstall.ts
2129
2275
  import { rmSync as rmSync2, statSync as statSync3, rmdirSync, readdirSync as readdirSync3 } from "node:fs";
2130
- import { dirname as dirname5, join as join7, resolve as resolve7 } from "node:path";
2276
+ import { dirname as dirname5, join as join8, resolve as resolve7 } from "node:path";
2131
2277
  function isJsonMode7(args) {
2132
2278
  return args.flags.json === true || args.flags.json === "true";
2133
2279
  }
@@ -2188,7 +2334,7 @@ async function uninstall(args) {
2188
2334
  const skipped = [];
2189
2335
  const dirsToCheck = /* @__PURE__ */ new Set();
2190
2336
  for (const file of state.files_owned) {
2191
- const fullPath = join7(root, file);
2337
+ const fullPath = join8(root, file);
2192
2338
  if (!pathExists(fullPath)) {
2193
2339
  skipped.push({ path: file, reason: "file not found" });
2194
2340
  continue;
@@ -2442,8 +2588,8 @@ function printHumanReadable7(r) {
2442
2588
  }
2443
2589
 
2444
2590
  // src/commands/production-auth.ts
2445
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
2446
- import { join as join8, resolve as resolve9 } from "node:path";
2591
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
2592
+ import { join as join9, resolve as resolve9 } from "node:path";
2447
2593
  function isJsonMode9(args) {
2448
2594
  return args.flags.json === true || args.flags.json === "true";
2449
2595
  }
@@ -2454,17 +2600,17 @@ function projectPath8(args) {
2454
2600
  }
2455
2601
  var SUPPORTED_FRAMEWORKS = /* @__PURE__ */ new Set(["nextjs", "react-router", "astro"]);
2456
2602
  function productionRoot() {
2457
- return join8(findRegistryRoot(), "v6", "production");
2603
+ return join9(findRegistryRoot(), "v6", "production");
2458
2604
  }
2459
2605
  function loadProductionManifest(framework) {
2460
2606
  const root = productionRoot();
2461
- const manifestPath = join8(root, `${framework}.template.json`);
2462
- const content = readFileSync7(manifestPath, "utf8");
2607
+ const manifestPath = join9(root, `${framework}.template.json`);
2608
+ const content = readFileSync8(manifestPath, "utf8");
2463
2609
  return JSON.parse(content);
2464
2610
  }
2465
2611
  function readProductionTemplate(framework, ref) {
2466
2612
  const root = productionRoot();
2467
- return readFileSync7(join8(root, framework, ref), "utf8");
2613
+ return readFileSync8(join9(root, framework, ref), "utf8");
2468
2614
  }
2469
2615
  function findClientLoginFile(state) {
2470
2616
  const candidates = [
@@ -2495,16 +2641,16 @@ ${indent} .catch((e: unknown) => setError(String(e)));`
2495
2641
  function clientPatch(root, state) {
2496
2642
  const relPath = findClientLoginFile(state);
2497
2643
  if (relPath === null) return { status: "n/a" };
2498
- const fullPath = join8(root, relPath);
2644
+ const fullPath = join9(root, relPath);
2499
2645
  let content;
2500
2646
  try {
2501
- content = readFileSync7(fullPath, "utf8");
2647
+ content = readFileSync8(fullPath, "utf8");
2502
2648
  } catch {
2503
2649
  return { status: "skipped", file: relPath };
2504
2650
  }
2505
2651
  const next = rewriteLoginChain(content);
2506
2652
  if (next === null) return { status: "skipped", file: relPath };
2507
- writeFileSync4(fullPath, next, "utf8");
2653
+ writeFileSync5(fullPath, next, "utf8");
2508
2654
  return { status: "applied", file: relPath, newChecksum: sha256(next) };
2509
2655
  }
2510
2656
  async function productionAuth(args) {
@@ -3041,19 +3187,19 @@ function printHumanReadable9(r) {
3041
3187
  }
3042
3188
 
3043
3189
  // src/commands/features.ts
3044
- import { readFileSync as readFileSync8 } from "node:fs";
3045
- import { join as join9 } from "node:path";
3190
+ import { readFileSync as readFileSync9 } from "node:fs";
3191
+ import { join as join10 } from "node:path";
3046
3192
  function isJsonMode11(args) {
3047
3193
  return args.flags.json === true || args.flags.json === "true";
3048
3194
  }
3049
3195
  function catalogPath() {
3050
- return join9(findRegistryRoot(), "v6", "features", "catalog.json");
3196
+ return join10(findRegistryRoot(), "v6", "features", "catalog.json");
3051
3197
  }
3052
3198
  var cachedCatalog = null;
3053
3199
  function loadCatalog() {
3054
3200
  if (cachedCatalog) return cachedCatalog;
3055
3201
  const path = catalogPath();
3056
- cachedCatalog = JSON.parse(readFileSync8(path, "utf8"));
3202
+ cachedCatalog = JSON.parse(readFileSync9(path, "utf8"));
3057
3203
  return cachedCatalog;
3058
3204
  }
3059
3205
  function nextStepsForFeature(feature) {
@@ -3066,6 +3212,7 @@ function nextStepsForFeature(feature) {
3066
3212
  ...feature.docs_topic ? [`For details, query the docs MCP for "${feature.docs_topic}" or visit https://www.cometchat.com/docs/ui-kit/react/${feature.docs_topic}`] : []
3067
3213
  ];
3068
3214
  case "dashboard-toggle": {
3215
+ const docsUrl = feature.docs_topic ? `https://www.cometchat.com/docs/ui-kit/react/${feature.docs_topic}` : "https://www.cometchat.com/docs/ui-kit/react/extensions";
3069
3216
  const lines = [];
3070
3217
  if (feature.auto_wired_in_uikit) {
3071
3218
  lines.push(
@@ -3074,8 +3221,13 @@ function nextStepsForFeature(feature) {
3074
3221
  "Steps to enable:",
3075
3222
  ` 1. Go to https://app.cometchat.com and select your app`,
3076
3223
  ` 2. Navigate to: ${feature.dashboard_path ?? "Extensions \u2192 " + feature.name}`,
3224
+ ` (this is a hint \u2014 if the dashboard UI has changed, see the canonical docs link below)`,
3077
3225
  ` 3. Toggle the feature on (and configure any required values)`,
3078
- ` 4. Refresh your dev server \u2014 no code changes required`
3226
+ ` 4. Refresh your dev server \u2014 no code changes required`,
3227
+ "",
3228
+ ` \u{1F4D6} Canonical docs (always-current \u2014 use this if the dashboard navigation above doesn't match what you see):`,
3229
+ ` ${docsUrl}`,
3230
+ ` For exact, up-to-date integration steps, query the cometchat-docs MCP for "${feature.id}".`
3079
3231
  );
3080
3232
  } else {
3081
3233
  lines.push(
@@ -3083,23 +3235,14 @@ function nextStepsForFeature(feature) {
3083
3235
  "",
3084
3236
  "Steps to enable:",
3085
3237
  ` 1. Go to https://app.cometchat.com \u2192 ${feature.dashboard_path ?? "Extensions \u2192 " + feature.name}, toggle on`,
3086
- ` 2. In your CometChat init (the file that calls UIKitSettingsBuilder), add the extension:`,
3087
- ` import { CometChatUIKit, UIKitSettingsBuilder } from "@cometchat/chat-uikit-react";`,
3088
- ` // Pseudocode \u2014 match your existing builder chain:`,
3089
- ` const settings = new UIKitSettingsBuilder()`,
3090
- ` .setAppId(APP_ID)`,
3091
- ` .setRegion(REGION)`,
3092
- ` .setAuthKey(AUTH_KEY)`,
3093
- ` .subscribePresenceForAllUsers()`,
3094
- ` .setExtensions([/* your extension instance here */])`,
3095
- ` .build();`,
3096
- ` CometChatUIKit.init(settings);`,
3097
- ` 3. Restart your dev server`
3238
+ ` (dashboard hint \u2014 see canonical docs link below if the navigation has changed)`,
3239
+ ` 2. Register the extension via UIKitSettingsBuilder.setExtensions([...]) in your CometChat init.`,
3240
+ ` For the exact import path + extension class name + builder syntax, query the cometchat-docs MCP for "${feature.id}" \u2014 DO NOT invent the API from memory.`,
3241
+ ` 3. Restart your dev server`,
3242
+ "",
3243
+ ` \u{1F4D6} Canonical docs: ${docsUrl}`
3098
3244
  );
3099
3245
  }
3100
- if (feature.docs_topic) {
3101
- lines.push(` Docs: https://www.cometchat.com/docs/ui-kit/react/${feature.docs_topic}`);
3102
- }
3103
3246
  return lines;
3104
3247
  }
3105
3248
  case "package-install":
@@ -3377,6 +3520,14 @@ function buildIssues(args) {
3377
3520
  fix: "Add the missing variables to your .env file. The chat will fail to initialize without them."
3378
3521
  });
3379
3522
  }
3523
+ if (args.envPlaceholder.length > 0) {
3524
+ issues.push({
3525
+ severity: "warning",
3526
+ code: "env-placeholder",
3527
+ message: `${args.envPlaceholder.length} env var(s) still contain YOUR_*_HERE placeholders: ${args.envPlaceholder.join(", ")}`,
3528
+ fix: `Open ${args.envFilePath ?? ".env"} and replace each YOUR_*_HERE value with the real one from https://app.cometchat.com \u2192 Your App \u2192 API & Auth Keys. Without this, the chat will fail to initialize at runtime.`
3529
+ });
3530
+ }
3380
3531
  return issues;
3381
3532
  }
3382
3533
  async function doctor(args) {
@@ -3419,17 +3570,34 @@ async function doctor(args) {
3419
3570
  } else if (expectedEnvVars[2]) {
3420
3571
  envMissing.push(expectedEnvVars[2]);
3421
3572
  }
3573
+ const envFileName = stateInfo ? envFileNameFor(stateInfo.framework) : detected.framework ? envFileNameFor(detected.framework) : null;
3574
+ const envFilePath = envFileName && pathExists(p(root, envFileName)) ? envFileName : null;
3575
+ const envPlaceholder = [];
3576
+ if (envFilePath) {
3577
+ const content = readFileOrNull(p(root, envFilePath));
3578
+ if (content) {
3579
+ const pairs = parseEnvPairs(content);
3580
+ for (const expected of expectedEnvVars) {
3581
+ const value = pairs.get(expected);
3582
+ if (value !== void 0 && isPlaceholder(value)) {
3583
+ envPlaceholder.push(expected);
3584
+ }
3585
+ }
3586
+ }
3587
+ }
3422
3588
  const issues = buildIssues({
3423
3589
  drift,
3424
3590
  verify: verifyResult,
3425
3591
  envMissing,
3592
+ envPlaceholder,
3593
+ envFilePath,
3426
3594
  hasIntegration: stateInfo !== null
3427
3595
  });
3428
3596
  const errorCount = issues.filter((i) => i.severity === "error").length;
3429
3597
  const warnCount = issues.filter((i) => i.severity === "warning").length;
3430
- const status = errorCount > 0 ? "issues" : warnCount > 0 ? "warnings" : "healthy";
3598
+ const status2 = errorCount > 0 ? "issues" : warnCount > 0 ? "warnings" : "healthy";
3431
3599
  const result = {
3432
- status,
3600
+ status: status2,
3433
3601
  detect: {
3434
3602
  framework: detected.framework,
3435
3603
  framework_version: detected.framework_version,
@@ -3453,17 +3621,19 @@ async function doctor(args) {
3453
3621
  },
3454
3622
  environment: {
3455
3623
  env_vars_present: envPresent.filter(Boolean),
3456
- env_vars_missing: envMissing
3624
+ env_vars_missing: envMissing,
3625
+ env_vars_placeholder: envPlaceholder,
3626
+ env_file_path: envFilePath
3457
3627
  },
3458
3628
  issues,
3459
- summary: buildSummary(status, errorCount, warnCount, stateInfo !== null)
3629
+ summary: buildSummary(status2, errorCount, warnCount, stateInfo !== null)
3460
3630
  };
3461
3631
  return outputResult7(args, result, errorCount > 0 ? 1 : 0);
3462
3632
  }
3463
- function buildSummary(status, errors, warnings, integrated) {
3633
+ function buildSummary(status2, errors, warnings, integrated) {
3464
3634
  if (!integrated) return "No CometChat integration found. Run `cometchat apply` to get started.";
3465
- if (status === "healthy") return "\u2713 All checks pass. Integration is healthy.";
3466
- if (status === "warnings") return `\u26A0 ${warnings} warning(s) found. Integration works but has minor issues.`;
3635
+ if (status2 === "healthy") return "\u2713 All checks pass. Integration is healthy.";
3636
+ if (status2 === "warnings") return `\u26A0 ${warnings} warning(s) found. Integration works but has minor issues.`;
3467
3637
  return `\u2717 ${errors} error(s) and ${warnings} warning(s) found. Integration may not work correctly.`;
3468
3638
  }
3469
3639
  function outputResult7(args, result, exitCode) {
@@ -3636,13 +3806,10 @@ async function init(args) {
3636
3806
  `Run \`cometchat install\` (or pass --install next time) to install ${applyResult.deps_to_install.length} npm package(s).`
3637
3807
  );
3638
3808
  }
3639
- try {
3640
- const manifest = loadManifest(6, detected.framework, parseInt(experienceArg, 10));
3641
- if (manifest.env_vars.length > 0) {
3642
- const envNames = manifest.env_vars.map((v) => v.name.replace("{{ENV_PREFIX}}", detected.env_prefix ?? "")).join(", ");
3643
- result.next_actions.unshift(`Add these env vars to .env: ${envNames}`);
3644
- }
3645
- } catch {
3809
+ if (applyResult.env_file && applyResult.env_file.written.length > 0) {
3810
+ result.next_actions.unshift(
3811
+ `Open ${applyResult.env_file.path} and replace ${applyResult.env_file.written.length} YOUR_*_HERE placeholder(s) with real values from https://app.cometchat.com \u2192 Your App \u2192 API & Auth Keys.`
3812
+ );
3646
3813
  }
3647
3814
  return outputResult8(args, result, 0);
3648
3815
  }
@@ -3685,8 +3852,8 @@ function printHumanReadable11(r) {
3685
3852
  }
3686
3853
 
3687
3854
  // src/commands/add-widget.ts
3688
- import { readFileSync as readFileSync9 } from "node:fs";
3689
- import { join as join10, resolve as resolve13 } from "node:path";
3855
+ import { readFileSync as readFileSync10 } from "node:fs";
3856
+ import { join as join11, resolve as resolve13 } from "node:path";
3690
3857
  var SUPPORTED = /* @__PURE__ */ new Set(["reactjs", "nextjs", "react-router", "astro"]);
3691
3858
  function isJsonMode14(args) {
3692
3859
  return args.flags.json === true || args.flags.json === "true";
@@ -3697,16 +3864,16 @@ function projectPath12(args) {
3697
3864
  return resolve13(process.cwd());
3698
3865
  }
3699
3866
  function widgetRoot() {
3700
- return join10(findRegistryRoot(), "v6", "widget");
3867
+ return join11(findRegistryRoot(), "v6", "widget");
3701
3868
  }
3702
3869
  function loadWidgetManifest(framework) {
3703
3870
  const root = widgetRoot();
3704
- const path = join10(root, `${framework}.template.json`);
3705
- return JSON.parse(readFileSync9(path, "utf8"));
3871
+ const path = join11(root, `${framework}.template.json`);
3872
+ return JSON.parse(readFileSync10(path, "utf8"));
3706
3873
  }
3707
3874
  function readWidgetTemplate(framework, ref) {
3708
3875
  const root = widgetRoot();
3709
- return readFileSync9(join10(root, framework, ref), "utf8");
3876
+ return readFileSync10(join11(root, framework, ref), "utf8");
3710
3877
  }
3711
3878
  async function addWidget(args) {
3712
3879
  const root = projectPath12(args);
@@ -3840,8 +4007,8 @@ function printHumanReadable12(r) {
3840
4007
  }
3841
4008
 
3842
4009
  // src/commands/add-user-mgmt.ts
3843
- import { readFileSync as readFileSync10 } from "node:fs";
3844
- import { join as join11, resolve as resolve14 } from "node:path";
4010
+ import { readFileSync as readFileSync11 } from "node:fs";
4011
+ import { join as join12, resolve as resolve14 } from "node:path";
3845
4012
  var SUPPORTED2 = /* @__PURE__ */ new Set(["nextjs", "react-router", "astro"]);
3846
4013
  function isJsonMode15(args) {
3847
4014
  return args.flags.json === true || args.flags.json === "true";
@@ -3852,15 +4019,15 @@ function projectPath13(args) {
3852
4019
  return resolve14(process.cwd());
3853
4020
  }
3854
4021
  function userMgmtRoot() {
3855
- return join11(findRegistryRoot(), "v6", "user-mgmt");
4022
+ return join12(findRegistryRoot(), "v6", "user-mgmt");
3856
4023
  }
3857
4024
  function loadManifest2(framework) {
3858
4025
  const root = userMgmtRoot();
3859
- return JSON.parse(readFileSync10(join11(root, `${framework}.template.json`), "utf8"));
4026
+ return JSON.parse(readFileSync11(join12(root, `${framework}.template.json`), "utf8"));
3860
4027
  }
3861
4028
  function readTemplate(framework, ref) {
3862
4029
  const root = userMgmtRoot();
3863
- return readFileSync10(join11(root, framework, ref), "utf8");
4030
+ return readFileSync11(join12(root, framework, ref), "utf8");
3864
4031
  }
3865
4032
  async function addUserMgmt(args) {
3866
4033
  const root = projectPath13(args);
@@ -4017,8 +4184,8 @@ function printHumanReadable13(r) {
4017
4184
  }
4018
4185
 
4019
4186
  // src/commands/apply-feature.ts
4020
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "node:fs";
4021
- import { join as join12, resolve as resolve15 } from "node:path";
4187
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "node:fs";
4188
+ import { join as join13, resolve as resolve15 } from "node:path";
4022
4189
  function isJsonMode16(args) {
4023
4190
  return args.flags.json === true || args.flags.json === "true";
4024
4191
  }
@@ -4028,10 +4195,10 @@ function projectPath14(args) {
4028
4195
  return resolve15(process.cwd());
4029
4196
  }
4030
4197
  function catalogPath2() {
4031
- return join12(findRegistryRoot(), "v6", "features", "catalog.json");
4198
+ return join13(findRegistryRoot(), "v6", "features", "catalog.json");
4032
4199
  }
4033
4200
  function loadCatalog2() {
4034
- return JSON.parse(readFileSync11(catalogPath2(), "utf8"));
4201
+ return JSON.parse(readFileSync12(catalogPath2(), "utf8"));
4035
4202
  }
4036
4203
  function swapIdentifier(content, from, to) {
4037
4204
  const escaped = from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -4107,16 +4274,16 @@ async function applyFeature(args) {
4107
4274
  const modified = [];
4108
4275
  let totalReplacements = 0;
4109
4276
  for (const relPath of state.files_owned) {
4110
- const fullPath = join12(root, relPath);
4277
+ const fullPath = join13(root, relPath);
4111
4278
  let content;
4112
4279
  try {
4113
- content = readFileSync11(fullPath, "utf8");
4280
+ content = readFileSync12(fullPath, "utf8");
4114
4281
  } catch {
4115
4282
  continue;
4116
4283
  }
4117
4284
  const [next, count] = swapIdentifier(content, feature.swap_from, feature.swap_to);
4118
4285
  if (count > 0) {
4119
- writeFileSync5(fullPath, next, "utf8");
4286
+ writeFileSync6(fullPath, next, "utf8");
4120
4287
  newChecksums[relPath] = sha256(next);
4121
4288
  modified.push(relPath);
4122
4289
  totalReplacements += count;
@@ -4188,6 +4355,295 @@ function printHumanReadable14(r) {
4188
4355
  console.log(lines.join("\n"));
4189
4356
  }
4190
4357
 
4358
+ // src/commands/status.ts
4359
+ import { readFileSync as readFileSync13 } from "node:fs";
4360
+ import { resolve as resolve16 } from "node:path";
4361
+ function isJsonMode17(args) {
4362
+ return args.flags.json === true || args.flags.json === "true";
4363
+ }
4364
+ function projectPath15(args) {
4365
+ const fromFlag = args.flags.path;
4366
+ if (typeof fromFlag === "string") return resolve16(fromFlag);
4367
+ return resolve16(process.cwd());
4368
+ }
4369
+ function widgetMarkerPath(state) {
4370
+ switch (state.framework) {
4371
+ case "nextjs":
4372
+ return state.router === "pages" ? "src/cometchat/CometChatWidget.tsx" : "src/app/cometchat/CometChatWidget.tsx";
4373
+ case "react-router":
4374
+ return "app/cometchat/CometChatWidget.tsx";
4375
+ case "astro":
4376
+ return "src/cometchat/CometChatWidget.tsx";
4377
+ default:
4378
+ return "src/cometchat/CometChatWidget.tsx";
4379
+ }
4380
+ }
4381
+ function productionAuthMarkerPath(state) {
4382
+ switch (state.framework) {
4383
+ case "nextjs":
4384
+ return state.router === "pages" ? "src/pages/api/cometchat-token.ts" : "src/app/api/cometchat-token/route.ts";
4385
+ case "react-router":
4386
+ return "app/routes/api.cometchat-token.ts";
4387
+ case "astro":
4388
+ return "src/pages/api/cometchat-token.ts";
4389
+ default:
4390
+ return "";
4391
+ }
4392
+ }
4393
+ function userMgmtMarkerPath(state) {
4394
+ switch (state.framework) {
4395
+ case "nextjs":
4396
+ return state.router === "pages" ? "src/pages/api/cometchat-user.ts" : "src/app/api/cometchat-user/route.ts";
4397
+ case "react-router":
4398
+ return "app/routes/api.cometchat-user.ts";
4399
+ case "astro":
4400
+ return "src/pages/api/cometchat-user.ts";
4401
+ default:
4402
+ return "";
4403
+ }
4404
+ }
4405
+ function themeFilePath(state) {
4406
+ switch (state.framework) {
4407
+ case "nextjs":
4408
+ return state.router === "pages" ? "src/styles/globals.css" : "src/app/globals.css";
4409
+ case "react-router":
4410
+ return "app/app.css";
4411
+ case "astro":
4412
+ return "src/cometchat/ChatApp.tsx";
4413
+ // theme block lives inside the React island
4414
+ default:
4415
+ return "src/index.css";
4416
+ }
4417
+ }
4418
+ function fileContains(root, relPath, marker) {
4419
+ const fullPath = p(root, relPath);
4420
+ if (!pathExists(fullPath)) return false;
4421
+ try {
4422
+ const content = readFileSync13(fullPath, "utf8");
4423
+ return content.includes(marker);
4424
+ } catch {
4425
+ return false;
4426
+ }
4427
+ }
4428
+ function countAuditEntries(root) {
4429
+ const path = p(root, AUDIT_LOG_PATH);
4430
+ if (!pathExists(path)) return 0;
4431
+ try {
4432
+ const content = readFileSync13(path, "utf8");
4433
+ const matches = content.match(/^## \d{4}-\d{2}-\d{2}T/gm);
4434
+ return matches ? matches.length : 0;
4435
+ } catch {
4436
+ return 0;
4437
+ }
4438
+ }
4439
+ async function status(args) {
4440
+ const root = projectPath15(args);
4441
+ if (!hasState(root)) {
4442
+ const result2 = {
4443
+ integrated: false,
4444
+ items: [],
4445
+ audit_log_entries: 0,
4446
+ audit_log_path: null,
4447
+ files_owned_count: 0,
4448
+ drift_count: 0,
4449
+ next_suggested: "Run `cometchat init` to scaffold a CometChat integration in this project."
4450
+ };
4451
+ return outputResult12(args, result2, 0);
4452
+ }
4453
+ const state = readState(root);
4454
+ if (!state) {
4455
+ const result2 = {
4456
+ integrated: false,
4457
+ items: [],
4458
+ audit_log_entries: 0,
4459
+ audit_log_path: null,
4460
+ files_owned_count: 0,
4461
+ drift_count: 0,
4462
+ next_suggested: "Could not parse .cometchat/state.json \u2014 schema mismatch?"
4463
+ };
4464
+ return outputResult12(args, result2, 1);
4465
+ }
4466
+ const items = [];
4467
+ items.push({
4468
+ step: 2,
4469
+ state: "done",
4470
+ label: "Base integration",
4471
+ detail: `experience ${state.experience} (${state.experience_slug}) on ${state.framework}${state.router ? ` (${state.router} router)` : ""}`
4472
+ });
4473
+ for (const feature of state.applied_features ?? []) {
4474
+ items.push({
4475
+ step: 4,
4476
+ state: "done",
4477
+ label: "Feature applied",
4478
+ detail: feature
4479
+ });
4480
+ }
4481
+ const widgetPath = widgetMarkerPath(state);
4482
+ if (widgetPath && pathExists(p(root, widgetPath))) {
4483
+ items.push({
4484
+ step: 4,
4485
+ state: "done",
4486
+ label: "Floating chat widget",
4487
+ detail: widgetPath
4488
+ });
4489
+ } else {
4490
+ items.push({
4491
+ step: 4,
4492
+ state: "todo",
4493
+ label: "Floating chat widget",
4494
+ detail: "not added",
4495
+ next_command: "cometchat add-widget"
4496
+ });
4497
+ }
4498
+ const userMgmtPath = userMgmtMarkerPath(state);
4499
+ if (state.framework === "reactjs") {
4500
+ items.push({
4501
+ step: 4,
4502
+ state: "skipped",
4503
+ label: "Server user management",
4504
+ detail: "reactjs has no built-in server (BYO backend)"
4505
+ });
4506
+ } else if (userMgmtPath && pathExists(p(root, userMgmtPath))) {
4507
+ items.push({
4508
+ step: 4,
4509
+ state: "done",
4510
+ label: "Server user management",
4511
+ detail: userMgmtPath
4512
+ });
4513
+ } else {
4514
+ items.push({
4515
+ step: 4,
4516
+ state: "todo",
4517
+ label: "Server user management",
4518
+ detail: "no /api/cometchat-user endpoint",
4519
+ next_command: "cometchat add-user-mgmt"
4520
+ });
4521
+ }
4522
+ const prodAuthPath = productionAuthMarkerPath(state);
4523
+ if (state.framework === "reactjs") {
4524
+ items.push({
4525
+ step: 5,
4526
+ state: "skipped",
4527
+ label: "Production auth",
4528
+ detail: "reactjs has no built-in server (BYO backend)"
4529
+ });
4530
+ } else if (state.credentials_source === "token-endpoint" || prodAuthPath && pathExists(p(root, prodAuthPath))) {
4531
+ items.push({
4532
+ step: 5,
4533
+ state: "done",
4534
+ label: "Production auth",
4535
+ detail: prodAuthPath || "server-side token endpoint"
4536
+ });
4537
+ } else {
4538
+ items.push({
4539
+ step: 5,
4540
+ state: "todo",
4541
+ label: "Production auth",
4542
+ detail: "still on dev Auth Key (insecure for prod)",
4543
+ next_command: "cometchat production-auth"
4544
+ });
4545
+ }
4546
+ const drift = detectDrift(root, state);
4547
+ const customizationCount = drift.modified_files.length;
4548
+ items.push({
4549
+ step: 5,
4550
+ state: customizationCount > 0 ? "done" : "todo",
4551
+ label: "Customizations",
4552
+ detail: customizationCount > 0 ? `${customizationCount} file modification(s) outside templates` : "0 modifications (still on default templates)",
4553
+ next_command: customizationCount > 0 ? void 0 : void 0
4554
+ });
4555
+ const themePath = themeFilePath(state);
4556
+ if (themePath && fileContains(root, themePath, "CometChat theme overrides")) {
4557
+ items.push({
4558
+ step: 7,
4559
+ state: "done",
4560
+ label: "Theme",
4561
+ detail: `applied to ${themePath}`
4562
+ });
4563
+ } else {
4564
+ items.push({
4565
+ step: 7,
4566
+ state: "todo",
4567
+ label: "Theme",
4568
+ detail: "no overrides applied (still on UI Kit defaults)",
4569
+ next_command: "cometchat apply-theme --preset slack"
4570
+ });
4571
+ }
4572
+ let nextSuggested;
4573
+ const todoItems = items.filter((i) => i.state === "todo" && i.next_command);
4574
+ const prodAuthTodo = todoItems.find((i) => i.label === "Production auth");
4575
+ const widgetTodo = todoItems.find((i) => i.label === "Floating chat widget");
4576
+ const themeTodo = todoItems.find((i) => i.label === "Theme");
4577
+ if (prodAuthTodo) {
4578
+ nextSuggested = `Run \`${prodAuthTodo.next_command}\` before deploying to production \u2014 your dev Auth Key is currently exposed to the browser.`;
4579
+ } else if (themeTodo) {
4580
+ nextSuggested = `Run \`${themeTodo.next_command}\` to brand the chat UI (or pick a different preset: whatsapp, imessage, discord, notion).`;
4581
+ } else if (widgetTodo) {
4582
+ nextSuggested = `Run \`${widgetTodo.next_command}\` to add a floating chat overlay.`;
4583
+ } else if (todoItems.length === 0) {
4584
+ nextSuggested = "Looks complete! Run `cometchat verify` for a final correctness check.";
4585
+ } else {
4586
+ nextSuggested = `Run \`/cometchat\` to open the iteration menu and pick what's next.`;
4587
+ }
4588
+ const result = {
4589
+ integrated: true,
4590
+ framework: state.framework,
4591
+ framework_version: state.framework_version,
4592
+ experience: state.experience,
4593
+ experience_slug: state.experience_slug,
4594
+ router: state.router,
4595
+ items,
4596
+ audit_log_entries: countAuditEntries(root),
4597
+ audit_log_path: pathExists(p(root, AUDIT_LOG_PATH)) ? AUDIT_LOG_PATH : null,
4598
+ files_owned_count: state.files_owned.length,
4599
+ drift_count: drift.modified_files.length,
4600
+ next_suggested: nextSuggested
4601
+ };
4602
+ return outputResult12(args, result, 0);
4603
+ }
4604
+ function outputResult12(args, result, exitCode) {
4605
+ if (isJsonMode17(args)) {
4606
+ console.log(JSON.stringify(result, null, 2));
4607
+ return exitCode;
4608
+ }
4609
+ printHumanReadable15(result);
4610
+ return exitCode;
4611
+ }
4612
+ function printHumanReadable15(r) {
4613
+ const lines = [];
4614
+ lines.push("");
4615
+ if (!r.integrated) {
4616
+ lines.push(" CometChat React integration \u2014 not yet started");
4617
+ lines.push("");
4618
+ if (r.next_suggested) lines.push(` \u2192 ${r.next_suggested}`);
4619
+ lines.push("");
4620
+ console.log(lines.join("\n"));
4621
+ return;
4622
+ }
4623
+ lines.push(` CometChat React integration \u2014 your progress`);
4624
+ lines.push("");
4625
+ for (const item of r.items) {
4626
+ const icon = item.state === "done" ? "\u2713" : item.state === "skipped" ? "\u2014" : "\u25CB";
4627
+ const stepCol = `Step ${item.step}:`.padEnd(8);
4628
+ const labelCol = item.label.padEnd(28);
4629
+ const detail = item.detail ? ` (${item.detail})` : "";
4630
+ lines.push(` ${icon} ${stepCol} ${labelCol}${detail}`);
4631
+ }
4632
+ lines.push("");
4633
+ lines.push(
4634
+ ` Audit log: ${r.audit_log_path ?? "none"}` + (r.audit_log_path ? ` (${r.audit_log_entries} entr${r.audit_log_entries === 1 ? "y" : "ies"})` : "")
4635
+ );
4636
+ lines.push(` Files owned: ${r.files_owned_count}`);
4637
+ lines.push(` Drift: ${r.drift_count > 0 ? `${r.drift_count} customized file(s)` : "none"}`);
4638
+ if (r.next_suggested) {
4639
+ lines.push("");
4640
+ lines.push(" Next suggested step:");
4641
+ lines.push(` \u2192 ${r.next_suggested}`);
4642
+ }
4643
+ lines.push("");
4644
+ console.log(lines.join("\n"));
4645
+ }
4646
+
4191
4647
  // src/index.ts
4192
4648
  var VERSION = "0.0.1";
4193
4649
  var HELP = `
@@ -4273,6 +4729,12 @@ Commands:
4273
4729
  for production sign-up flows. Currently nextjs,
4274
4730
  react-router, and astro.
4275
4731
 
4732
+ status Journey-shaped progress summary. Aggregates state +
4733
+ applied_features + file markers into a checklist
4734
+ that mirrors the React UI Kit Integration Journey
4735
+ (base, features, widget, prod-auth, theme, etc.)
4736
+ and suggests the highest-leverage next action.
4737
+
4276
4738
  apply-feature <id> Apply a component-swap feature on top of the
4277
4739
  current integration (e.g.
4278
4740
  \`apply-feature rich-text-formatting\` swaps
@@ -4373,6 +4835,8 @@ async function run(argv) {
4373
4835
  return addUserMgmt(args);
4374
4836
  case "apply-feature":
4375
4837
  return applyFeature(args);
4838
+ case "status":
4839
+ return status(args);
4376
4840
  default:
4377
4841
  console.error(`Unknown command: ${args.command}`);
4378
4842
  console.error("Run `cometchat --help` for usage.");
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "version": "1.0.0",
4
4
  "sdk_compatibility": ">=6.0.0 <7.0.0",
5
- "description": "Catalog of CometChat features available in the React UI Kit. Categorized by what work is needed to enable each one.",
5
+ "description": "Catalog of CometChat features available in the React UI Kit. Categorized by what work is needed to enable each one. NOTE: this catalog stores OUR taxonomy (default vs dashboard-toggle vs package-install vs component-swap) plus a `dashboard_path` HINT for navigation. The hint is short and intentionally fragile — the canonical source is always the docs URL constructed from `docs_topic`. Agents must query the cometchat-docs MCP at runtime for current dashboard navigation, prop signatures, builder methods, and SDK reference. This catalog is for routing, not for SDK reference.",
6
6
  "feature_types": {
7
7
  "default": "Already enabled by default in the UI Kit components your integration uses. Zero code changes needed — the skill just shows you which component renders it.",
8
8
  "dashboard-toggle": "Requires flipping a toggle in the CometChat Dashboard at https://app.cometchat.com. Some have their UI decorator auto-attached by the UI Kit (auto_wired_in_uikit: true) — the dashboard flip is the only thing needed. Others additionally require passing the extension to UIKitSettingsBuilder.setExtensions([...]) before init.",
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@cometchat/skills-cli",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "CLI for the CometChat skills v2 architecture — detect, view, apply, verify CometChat integrations in React projects.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "bin": {
8
- "cometchat": "./bin/cometchat.mjs"
8
+ "cometchat": "bin/cometchat.mjs"
9
9
  },
10
10
  "files": [
11
11
  "bin/",