@cometchat/skills-cli 2.2.0 → 2.3.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 +703 -64
- package/dist/registry/v6/features/catalog.json +33 -25
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -283,6 +283,9 @@ var init_state = __esm({
|
|
|
283
283
|
// src/commands/detect.ts
|
|
284
284
|
import { resolve } from "node:path";
|
|
285
285
|
|
|
286
|
+
// src/detectors/framework.ts
|
|
287
|
+
import { readdirSync } from "node:fs";
|
|
288
|
+
|
|
286
289
|
// src/utils/version.ts
|
|
287
290
|
function stripRange(versionRange) {
|
|
288
291
|
return versionRange.replace(/^[\^~>=<\s]+/, "").trim();
|
|
@@ -312,7 +315,9 @@ var EMPTY = {
|
|
|
312
315
|
env_prefix: null,
|
|
313
316
|
expo_mode: null,
|
|
314
317
|
expo_version: null,
|
|
315
|
-
react_native_version: null
|
|
318
|
+
react_native_version: null,
|
|
319
|
+
android_version: null,
|
|
320
|
+
flutter_version: null
|
|
316
321
|
};
|
|
317
322
|
function allDeps(pkg) {
|
|
318
323
|
return {
|
|
@@ -434,6 +439,144 @@ function detectAstro(_root, deps) {
|
|
|
434
439
|
env_prefix: "PUBLIC_"
|
|
435
440
|
};
|
|
436
441
|
}
|
|
442
|
+
function detectIOS(root) {
|
|
443
|
+
let dirEntries = [];
|
|
444
|
+
try {
|
|
445
|
+
dirEntries = readdirSync(root);
|
|
446
|
+
} catch {
|
|
447
|
+
}
|
|
448
|
+
const hasXcode = dirEntries.some(
|
|
449
|
+
(n) => n.endsWith(".xcodeproj") || n.endsWith(".xcworkspace")
|
|
450
|
+
);
|
|
451
|
+
const podfile = readFileOrNull(p(root, "Podfile"));
|
|
452
|
+
const hasIOSPodfile = !!podfile && /platform\s+:ios/.test(podfile);
|
|
453
|
+
const packageSwift = readFileOrNull(p(root, "Package.swift"));
|
|
454
|
+
const hasIOSPackage = !!packageSwift && /\.iOS\s*\(/.test(packageSwift);
|
|
455
|
+
if (!hasXcode && !hasIOSPodfile && !hasIOSPackage) return null;
|
|
456
|
+
const podfileLock = readFileOrNull(p(root, "Podfile.lock")) ?? "";
|
|
457
|
+
const packageResolved = readFileOrNull(p(root, "Package.resolved")) ?? readFileOrNull(p(root, ".swiftpm/Package.resolved")) ?? "";
|
|
458
|
+
const cocoapodSources = (podfile ?? "") + podfileLock;
|
|
459
|
+
const spmSources = (packageSwift ?? "") + packageResolved;
|
|
460
|
+
let ios_version = null;
|
|
461
|
+
if (
|
|
462
|
+
// Podfile.lock: `- CometChatUIKitSwift (5.1.12)` / Podfile: `pod 'CometChatUIKitSwift', '~> 5.0'`
|
|
463
|
+
/CometChatUIKitSwift[^,\n]*?[\s'"(]5\./.test(cocoapodSources) || // Package.resolved: `"identity" : "cometchatuikitswift", ... "version" : "5.x.x"`
|
|
464
|
+
/cometchatuikitswift[\s\S]{0,300}"version"\s*:\s*"5\./i.test(spmSources) || // Package.swift: `.package(url: "https://github.com/cometchat/...CometChatUIKitSwift", from: "5.0.0")`
|
|
465
|
+
/CometChatUIKitSwift[\s\S]{0,200}from:\s*"5\./.test(spmSources)
|
|
466
|
+
) {
|
|
467
|
+
ios_version = "v5";
|
|
468
|
+
}
|
|
469
|
+
return {
|
|
470
|
+
framework: "ios",
|
|
471
|
+
framework_version: null,
|
|
472
|
+
// Swift / iOS deployment target isn't load-bearing for the dispatcher
|
|
473
|
+
router: null,
|
|
474
|
+
bundler: null,
|
|
475
|
+
ssr_strategy: null,
|
|
476
|
+
env_prefix: "",
|
|
477
|
+
// No public-env-prefix; Secrets.swift / xcconfig is the convention
|
|
478
|
+
ios_version
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
function detectFlutter(root) {
|
|
482
|
+
const pubspecRaw = readFileOrNull(p(root, "pubspec.yaml"));
|
|
483
|
+
if (!pubspecRaw) return null;
|
|
484
|
+
const isFlutter = /\n\s*flutter:\s*\n\s*sdk:\s*flutter/.test(pubspecRaw) || /\n\s*flutter:\s*$/m.test(pubspecRaw) || /\n\s*flutter:\s*\n\s*uses-material-design/.test(pubspecRaw);
|
|
485
|
+
if (!isFlutter) return null;
|
|
486
|
+
let flutter_version = null;
|
|
487
|
+
if (/cometchat_chat_uikit\s*:\s*[\^~]?6\./.test(pubspecRaw)) {
|
|
488
|
+
flutter_version = "v6";
|
|
489
|
+
} else if (/cometchat_chat_uikit\s*:\s*[\^~]?5\./.test(pubspecRaw) || /cometchat_calls_uikit\s*:\s*[\^~]?5\./.test(pubspecRaw)) {
|
|
490
|
+
flutter_version = "v5";
|
|
491
|
+
}
|
|
492
|
+
const sdkMatch = pubspecRaw.match(/\n\s*environment:\s*\n\s*sdk:\s*['"]?([^'"\n]+)/);
|
|
493
|
+
const framework_version = sdkMatch ? sdkMatch[1].trim() : null;
|
|
494
|
+
return {
|
|
495
|
+
framework: "flutter",
|
|
496
|
+
framework_version,
|
|
497
|
+
router: null,
|
|
498
|
+
// Flutter routing is in code (Navigator/GoRouter); not modeled here
|
|
499
|
+
bundler: null,
|
|
500
|
+
// Dart compiler — no JS bundler
|
|
501
|
+
ssr_strategy: null,
|
|
502
|
+
env_prefix: "",
|
|
503
|
+
// No public-env-prefix; --dart-define or .env-derived constants
|
|
504
|
+
flutter_version
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
function detectAndroid(root) {
|
|
508
|
+
const hasSettingsGradle = pathExists(p(root, "settings.gradle")) || pathExists(p(root, "settings.gradle.kts"));
|
|
509
|
+
const hasBuildGradle = pathExists(p(root, "build.gradle")) || pathExists(p(root, "build.gradle.kts"));
|
|
510
|
+
if (!hasSettingsGradle && !hasBuildGradle) return null;
|
|
511
|
+
const hasAppModule = pathExists(p(root, "app/build.gradle")) || pathExists(p(root, "app/build.gradle.kts"));
|
|
512
|
+
const rootGradle = readFileOrNull(p(root, "build.gradle")) ?? readFileOrNull(p(root, "build.gradle.kts")) ?? "";
|
|
513
|
+
const settingsGradle = readFileOrNull(p(root, "settings.gradle")) ?? readFileOrNull(p(root, "settings.gradle.kts")) ?? "";
|
|
514
|
+
const hasAndroidPlugin = /com\.android\.(application|library)/.test(
|
|
515
|
+
rootGradle + settingsGradle
|
|
516
|
+
);
|
|
517
|
+
if (!hasAppModule && !hasAndroidPlugin) return null;
|
|
518
|
+
const buildFiles = [
|
|
519
|
+
"build.gradle",
|
|
520
|
+
"build.gradle.kts",
|
|
521
|
+
"app/build.gradle",
|
|
522
|
+
"app/build.gradle.kts"
|
|
523
|
+
];
|
|
524
|
+
let allBuildContent = "";
|
|
525
|
+
for (const f of buildFiles) {
|
|
526
|
+
const c = readFileOrNull(p(root, f));
|
|
527
|
+
if (c) allBuildContent += "\n" + c;
|
|
528
|
+
}
|
|
529
|
+
let android_version = null;
|
|
530
|
+
if (/com\.cometchat:chatuikit-(compose|kotlin)-android:6/.test(allBuildContent)) {
|
|
531
|
+
android_version = "v6";
|
|
532
|
+
} else if (/com\.cometchat:chat-uikit-android:5/.test(allBuildContent)) {
|
|
533
|
+
android_version = "v5";
|
|
534
|
+
}
|
|
535
|
+
let android_language = null;
|
|
536
|
+
const usesKtsGradle = pathExists(p(root, "build.gradle.kts")) || pathExists(p(root, "app/build.gradle.kts")) || pathExists(p(root, "settings.gradle.kts"));
|
|
537
|
+
const hasKotlinPlugin = /(?:apply\s+plugin\s*:\s*['"]kotlin-android['"]|id\s*\(?\s*['"]org\.jetbrains\.kotlin\.android['"]|id\s*\(?\s*['"]kotlin-android['"])/.test(
|
|
538
|
+
allBuildContent
|
|
539
|
+
);
|
|
540
|
+
if (usesKtsGradle || hasKotlinPlugin) {
|
|
541
|
+
android_language = "kotlin";
|
|
542
|
+
} else {
|
|
543
|
+
const javaDir = p(root, "app/src/main/java");
|
|
544
|
+
const kotlinDir = p(root, "app/src/main/kotlin");
|
|
545
|
+
if (pathExists(kotlinDir)) android_language = "kotlin";
|
|
546
|
+
else if (pathExists(javaDir)) android_language = "java";
|
|
547
|
+
}
|
|
548
|
+
return {
|
|
549
|
+
framework: "android",
|
|
550
|
+
framework_version: null,
|
|
551
|
+
// Gradle/AGP version isn't load-bearing for the dispatcher
|
|
552
|
+
router: null,
|
|
553
|
+
bundler: null,
|
|
554
|
+
// No JS bundler for native Android
|
|
555
|
+
ssr_strategy: null,
|
|
556
|
+
env_prefix: "",
|
|
557
|
+
// No public-env-prefix — local.properties + BuildConfig is the convention
|
|
558
|
+
android_version,
|
|
559
|
+
android_language
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
function detectAngular(root, deps) {
|
|
563
|
+
const hasAngularJson = pathExists(p(root, "angular.json"));
|
|
564
|
+
const hasAngularCore = !!deps["@angular/core"];
|
|
565
|
+
if (!hasAngularJson && !hasAngularCore) return null;
|
|
566
|
+
const version = extractVersion(deps["@angular/core"] ?? "");
|
|
567
|
+
return {
|
|
568
|
+
framework: "angular",
|
|
569
|
+
framework_version: version,
|
|
570
|
+
router: null,
|
|
571
|
+
// Angular Router is a separate concern; not modeled here yet
|
|
572
|
+
bundler: "webpack",
|
|
573
|
+
// Angular CLI ships with webpack; esbuild for v17+ but irrelevant to integration
|
|
574
|
+
ssr_strategy: "spa-no-ssr",
|
|
575
|
+
// Angular Universal exists but the integration path is SPA
|
|
576
|
+
env_prefix: ""
|
|
577
|
+
// No public-env-prefix — environment.ts is the convention
|
|
578
|
+
};
|
|
579
|
+
}
|
|
437
580
|
function detectReactjs(root, deps) {
|
|
438
581
|
if (!deps["react"]) return null;
|
|
439
582
|
const version = extractVersion(deps["react"]);
|
|
@@ -465,10 +608,16 @@ function detectUsesJsx(root) {
|
|
|
465
608
|
return false;
|
|
466
609
|
}
|
|
467
610
|
function detectFramework(root) {
|
|
611
|
+
const flutterInfo = detectFlutter(root);
|
|
612
|
+
if (flutterInfo) return flutterInfo;
|
|
613
|
+
const iosInfo = detectIOS(root);
|
|
614
|
+
if (iosInfo) return iosInfo;
|
|
615
|
+
const androidInfo = detectAndroid(root);
|
|
616
|
+
if (androidInfo) return androidInfo;
|
|
468
617
|
const pkg = readJsonOrNull(p(root, "package.json"));
|
|
469
618
|
if (!pkg) return EMPTY;
|
|
470
619
|
const deps = allDeps(pkg);
|
|
471
|
-
return detectExpo(root, deps, pkg) ?? detectBareReactNative(root, deps) ?? detectNextjs(root, deps) ?? detectAstro(root, deps) ?? detectReactRouter(root, deps) ?? detectReactjs(root, deps) ?? EMPTY;
|
|
620
|
+
return detectExpo(root, deps, pkg) ?? detectBareReactNative(root, deps) ?? detectNextjs(root, deps) ?? detectAstro(root, deps) ?? detectReactRouter(root, deps) ?? detectAngular(root, deps) ?? detectReactjs(root, deps) ?? EMPTY;
|
|
472
621
|
}
|
|
473
622
|
|
|
474
623
|
// src/detectors/package-manager.ts
|
|
@@ -483,7 +632,7 @@ function detectPackageManager(root) {
|
|
|
483
632
|
|
|
484
633
|
// src/detectors/credentials.ts
|
|
485
634
|
init_fs();
|
|
486
|
-
import { readdirSync, statSync } from "node:fs";
|
|
635
|
+
import { readdirSync as readdirSync2, statSync } from "node:fs";
|
|
487
636
|
import { join as join2, extname } from "node:path";
|
|
488
637
|
function envVarsForPrefix(prefix) {
|
|
489
638
|
const p2 = prefix ?? "";
|
|
@@ -551,7 +700,7 @@ function searchForInit(dir, maxDepth) {
|
|
|
551
700
|
if (maxDepth < 0) return false;
|
|
552
701
|
let entries;
|
|
553
702
|
try {
|
|
554
|
-
entries =
|
|
703
|
+
entries = readdirSync2(dir);
|
|
555
704
|
} catch {
|
|
556
705
|
return false;
|
|
557
706
|
}
|
|
@@ -603,7 +752,7 @@ function detectCredentials(root, envPrefix) {
|
|
|
603
752
|
}
|
|
604
753
|
|
|
605
754
|
// src/detectors/integration.ts
|
|
606
|
-
import { readdirSync as
|
|
755
|
+
import { readdirSync as readdirSync3 } from "node:fs";
|
|
607
756
|
init_fs();
|
|
608
757
|
function getInstalledVersion(root) {
|
|
609
758
|
const pkg = readJsonOrNull(p(root, "package.json"));
|
|
@@ -626,7 +775,7 @@ function findCometchatDirs(root) {
|
|
|
626
775
|
const fullPath = p(root, dir);
|
|
627
776
|
if (!pathExists(fullPath)) continue;
|
|
628
777
|
try {
|
|
629
|
-
const entries =
|
|
778
|
+
const entries = readdirSync3(fullPath);
|
|
630
779
|
for (const entry of entries) {
|
|
631
780
|
found.push(`${dir}/${entry}`);
|
|
632
781
|
}
|
|
@@ -818,6 +967,34 @@ function detectArchitectureContext(root) {
|
|
|
818
967
|
};
|
|
819
968
|
}
|
|
820
969
|
|
|
970
|
+
// src/detectors/project-name.ts
|
|
971
|
+
init_fs();
|
|
972
|
+
function detectProjectName(root) {
|
|
973
|
+
const pkg = readJsonOrNull(p(root, "package.json"));
|
|
974
|
+
if (pkg?.name) return cleanName(pkg.name);
|
|
975
|
+
const pubspec = readFileOrNull(p(root, "pubspec.yaml"));
|
|
976
|
+
if (pubspec) {
|
|
977
|
+
const m = pubspec.match(/^\s*name\s*:\s*['"]?([A-Za-z_][A-Za-z0-9_]*)['"]?\s*$/m);
|
|
978
|
+
if (m) return cleanName(m[1]);
|
|
979
|
+
}
|
|
980
|
+
const packageSwift = readFileOrNull(p(root, "Package.swift"));
|
|
981
|
+
if (packageSwift) {
|
|
982
|
+
const m = packageSwift.match(/Package\s*\(\s*name\s*:\s*"([^"]+)"/);
|
|
983
|
+
if (m) return cleanName(m[1]);
|
|
984
|
+
}
|
|
985
|
+
for (const file of ["settings.gradle.kts", "settings.gradle"]) {
|
|
986
|
+
const content = readFileOrNull(p(root, file));
|
|
987
|
+
if (!content) continue;
|
|
988
|
+
const m = content.match(/rootProject\.name\s*=\s*['"]([^'"\n]+)['"]/);
|
|
989
|
+
if (m) return cleanName(m[1]);
|
|
990
|
+
}
|
|
991
|
+
return null;
|
|
992
|
+
}
|
|
993
|
+
function cleanName(raw) {
|
|
994
|
+
const trimmed = raw.trim().slice(0, 64);
|
|
995
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
996
|
+
}
|
|
997
|
+
|
|
821
998
|
// src/detectors/index.ts
|
|
822
999
|
async function runDetectors(root) {
|
|
823
1000
|
const fw = detectFramework(root);
|
|
@@ -826,6 +1003,7 @@ async function runDetectors(root) {
|
|
|
826
1003
|
const existing_integration = detectExistingIntegration(root);
|
|
827
1004
|
const compatibility = computeCompatibility(fw);
|
|
828
1005
|
const architecture_context = detectArchitectureContext(root);
|
|
1006
|
+
const project_name = detectProjectName(root);
|
|
829
1007
|
return {
|
|
830
1008
|
project_root: root,
|
|
831
1009
|
framework: fw.framework,
|
|
@@ -839,6 +1017,11 @@ async function runDetectors(root) {
|
|
|
839
1017
|
expo_mode: fw.expo_mode,
|
|
840
1018
|
expo_version: fw.expo_version,
|
|
841
1019
|
react_native_version: fw.react_native_version,
|
|
1020
|
+
android_version: fw.android_version,
|
|
1021
|
+
android_language: fw.android_language,
|
|
1022
|
+
flutter_version: fw.flutter_version,
|
|
1023
|
+
ios_version: fw.ios_version,
|
|
1024
|
+
project_name,
|
|
842
1025
|
package_manager,
|
|
843
1026
|
credentials,
|
|
844
1027
|
existing_integration,
|
|
@@ -919,6 +1102,12 @@ function printHumanReadable(r) {
|
|
|
919
1102
|
if (r.react_native_version && r.framework === "expo") {
|
|
920
1103
|
lines.push(` React Native: ${r.react_native_version}`);
|
|
921
1104
|
}
|
|
1105
|
+
if (r.framework === "android") {
|
|
1106
|
+
lines.push(` Android UIKit: ${r.android_version ?? "(none yet \u2014 greenfield)"}`);
|
|
1107
|
+
}
|
|
1108
|
+
if (r.framework === "flutter") {
|
|
1109
|
+
lines.push(` Flutter UIKit: ${r.flutter_version ?? "(none yet \u2014 greenfield)"}`);
|
|
1110
|
+
}
|
|
922
1111
|
if (r.router !== null) lines.push(` Router: ${r.router}`);
|
|
923
1112
|
if (r.bundler !== null) lines.push(` Bundler: ${r.bundler}`);
|
|
924
1113
|
if (r.ssr_strategy !== null) lines.push(` SSR strategy: ${r.ssr_strategy}`);
|
|
@@ -3134,7 +3323,7 @@ function printHumanReadable5(r) {
|
|
|
3134
3323
|
// src/commands/uninstall.ts
|
|
3135
3324
|
init_state();
|
|
3136
3325
|
init_fs();
|
|
3137
|
-
import { rmSync as rmSync2, statSync as statSync3, rmdirSync, readdirSync as
|
|
3326
|
+
import { rmSync as rmSync2, statSync as statSync3, rmdirSync, readdirSync as readdirSync4 } from "node:fs";
|
|
3138
3327
|
import { dirname as dirname6, join as join9, resolve as resolve7 } from "node:path";
|
|
3139
3328
|
var HELP6 = `
|
|
3140
3329
|
cometchat uninstall \u2014 remove the CometChat integration cleanly
|
|
@@ -3168,7 +3357,7 @@ function projectPath6(args) {
|
|
|
3168
3357
|
}
|
|
3169
3358
|
function maybeRemoveEmptyDir(dir) {
|
|
3170
3359
|
try {
|
|
3171
|
-
const entries =
|
|
3360
|
+
const entries = readdirSync4(dir);
|
|
3172
3361
|
if (entries.length === 0) {
|
|
3173
3362
|
rmdirSync(dir);
|
|
3174
3363
|
maybeRemoveEmptyDir(dirname6(dir));
|
|
@@ -3245,7 +3434,7 @@ async function uninstall(args) {
|
|
|
3245
3434
|
try {
|
|
3246
3435
|
const cometchatDir = p(root, ".cometchat");
|
|
3247
3436
|
if (statSync3(cometchatDir).isDirectory()) {
|
|
3248
|
-
const entries =
|
|
3437
|
+
const entries = readdirSync4(cometchatDir);
|
|
3249
3438
|
if (entries.length === 0) rmdirSync(cometchatDir);
|
|
3250
3439
|
}
|
|
3251
3440
|
} catch {
|
|
@@ -5191,8 +5380,19 @@ async function listApps(host, token) {
|
|
|
5191
5380
|
const name = String(entry.name ?? id);
|
|
5192
5381
|
const region = String(entry.region ?? "us");
|
|
5193
5382
|
const plan = typeof entry.plan === "string" ? entry.plan : void 0;
|
|
5194
|
-
const
|
|
5195
|
-
|
|
5383
|
+
const meta = entry.metadata && typeof entry.metadata === "object" ? entry.metadata : {};
|
|
5384
|
+
const industry = typeof meta.industry === "string" ? meta.industry : void 0;
|
|
5385
|
+
const technology = typeof meta.technology === "string" ? meta.technology : void 0;
|
|
5386
|
+
const product = typeof meta.product === "string" ? meta.product : void 0;
|
|
5387
|
+
const createdAtRaw = entry.createdAt ?? entry.created_at;
|
|
5388
|
+
const createdAt = typeof createdAtRaw === "number" ? createdAtRaw : typeof createdAtRaw === "string" ? Number(new Date(createdAtRaw)) || void 0 : void 0;
|
|
5389
|
+
const out = { id, name, region };
|
|
5390
|
+
if (plan) out.plan = plan;
|
|
5391
|
+
if (industry) out.industry = industry;
|
|
5392
|
+
if (technology) out.technology = technology;
|
|
5393
|
+
if (product) out.product = product;
|
|
5394
|
+
if (createdAt !== void 0) out.createdAt = createdAt;
|
|
5395
|
+
return out;
|
|
5196
5396
|
});
|
|
5197
5397
|
}
|
|
5198
5398
|
async function getAppCredentials(host, token, appId) {
|
|
@@ -5219,6 +5419,40 @@ async function getAppCredentials(host, token, appId) {
|
|
|
5219
5419
|
}
|
|
5220
5420
|
return { appId: id, authKey, region };
|
|
5221
5421
|
}
|
|
5422
|
+
async function getCurrentUser(host, token) {
|
|
5423
|
+
const res = await request(host, "/me", {
|
|
5424
|
+
method: "GET",
|
|
5425
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
5426
|
+
});
|
|
5427
|
+
const body = await parseBody(res);
|
|
5428
|
+
if (!res.ok) {
|
|
5429
|
+
const { code, message } = extractErrorCode(body);
|
|
5430
|
+
if (res.status === 401) throw new Error("AUTH_FAILED");
|
|
5431
|
+
throw new Error(`API_ERROR: ${code} ${message}`);
|
|
5432
|
+
}
|
|
5433
|
+
const data = body?.data;
|
|
5434
|
+
if (!data || typeof data !== "object") {
|
|
5435
|
+
throw new Error(`API_ERROR: UNEXPECTED_SHAPE ${JSON.stringify(body)}`);
|
|
5436
|
+
}
|
|
5437
|
+
return data;
|
|
5438
|
+
}
|
|
5439
|
+
async function getCurrentUserWithLastApp(host, token) {
|
|
5440
|
+
const user = await getCurrentUser(host, token);
|
|
5441
|
+
let last_app = null;
|
|
5442
|
+
try {
|
|
5443
|
+
const apps = await listApps(host, token);
|
|
5444
|
+
if (apps.length > 0) {
|
|
5445
|
+
const sorted = [...apps].sort((a, b) => {
|
|
5446
|
+
const aT = a.createdAt ?? 0;
|
|
5447
|
+
const bT = b.createdAt ?? 0;
|
|
5448
|
+
return bT - aT;
|
|
5449
|
+
});
|
|
5450
|
+
last_app = sorted[0];
|
|
5451
|
+
}
|
|
5452
|
+
} catch {
|
|
5453
|
+
}
|
|
5454
|
+
return { ...user, last_app };
|
|
5455
|
+
}
|
|
5222
5456
|
async function listInstalledExtensions(host, token, appId) {
|
|
5223
5457
|
const res = await request(
|
|
5224
5458
|
host,
|
|
@@ -5249,6 +5483,49 @@ async function toggleExtension(host, token, appId, extensionId, action) {
|
|
|
5249
5483
|
throw new Error(`API_ERROR: ${code} ${message}`);
|
|
5250
5484
|
}
|
|
5251
5485
|
}
|
|
5486
|
+
async function toggleAiFeature(host, token, appId, featureKey, action) {
|
|
5487
|
+
const path = `/apps/${encodeURIComponent(appId)}/features/ai.${encodeURIComponent(featureKey)}/enabled`;
|
|
5488
|
+
const res = await request(host, path, {
|
|
5489
|
+
method: action === "enable" ? "POST" : "DELETE",
|
|
5490
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
5491
|
+
body: action === "enable" ? "{}" : void 0
|
|
5492
|
+
});
|
|
5493
|
+
if (!res.ok) {
|
|
5494
|
+
const body = await parseBody(res);
|
|
5495
|
+
const { code, message } = extractErrorCode(body);
|
|
5496
|
+
if (res.status === 401) throw new Error("AUTH_FAILED");
|
|
5497
|
+
throw new Error(`API_ERROR: ${code} ${message}`);
|
|
5498
|
+
}
|
|
5499
|
+
}
|
|
5500
|
+
async function getAiSettings(host, token, appId) {
|
|
5501
|
+
const res = await request(host, `/apps/${encodeURIComponent(appId)}/ai/settings`, {
|
|
5502
|
+
method: "GET",
|
|
5503
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
5504
|
+
});
|
|
5505
|
+
const body = await parseBody(res);
|
|
5506
|
+
if (!res.ok) {
|
|
5507
|
+
const { code, message } = extractErrorCode(body);
|
|
5508
|
+
if (res.status === 401) throw new Error("AUTH_FAILED");
|
|
5509
|
+
if (res.status === 404) return null;
|
|
5510
|
+
throw new Error(`API_ERROR: ${code} ${message}`);
|
|
5511
|
+
}
|
|
5512
|
+
const data = body?.data;
|
|
5513
|
+
if (!data || typeof data !== "object") return null;
|
|
5514
|
+
return data;
|
|
5515
|
+
}
|
|
5516
|
+
async function updateAiSettings(host, token, appId, settings) {
|
|
5517
|
+
const res = await request(host, `/apps/${encodeURIComponent(appId)}/ai/settings`, {
|
|
5518
|
+
method: "PUT",
|
|
5519
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
5520
|
+
body: JSON.stringify(settings)
|
|
5521
|
+
});
|
|
5522
|
+
if (!res.ok) {
|
|
5523
|
+
const body = await parseBody(res);
|
|
5524
|
+
const { code, message } = extractErrorCode(body);
|
|
5525
|
+
if (res.status === 401) throw new Error("AUTH_FAILED");
|
|
5526
|
+
throw new Error(`API_ERROR: ${code} ${message}`);
|
|
5527
|
+
}
|
|
5528
|
+
}
|
|
5252
5529
|
async function createApp(host, token, params) {
|
|
5253
5530
|
const res = await request(host, "/apps", {
|
|
5254
5531
|
method: "POST",
|
|
@@ -5347,40 +5624,58 @@ function nextStepsForFeature(feature, framework = "reactjs") {
|
|
|
5347
5624
|
feature.requires_dashboard_setup ? "Some features (like moderation) need rules configured in the CometChat dashboard before they activate. Check the dashboard." : "No code changes needed \u2014 it's already there.",
|
|
5348
5625
|
...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}`] : []
|
|
5349
5626
|
];
|
|
5350
|
-
case "
|
|
5627
|
+
case "extension": {
|
|
5351
5628
|
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";
|
|
5352
5629
|
const lines = [];
|
|
5353
5630
|
if (feature.auto_wired_in_uikit) {
|
|
5354
5631
|
lines.push(
|
|
5355
|
-
`${feature.name} is in the UI Kit's defaultExtensions[] \u2014 its UI decorator is attached automatically by initiateAfterLogin()
|
|
5632
|
+
`${feature.name} is in the UI Kit's defaultExtensions[] \u2014 its UI decorator is attached automatically by initiateAfterLogin().`,
|
|
5356
5633
|
"",
|
|
5357
5634
|
"Steps to enable:",
|
|
5358
|
-
` 1.
|
|
5359
|
-
` 2.
|
|
5360
|
-
` (this is a hint \u2014 if the dashboard UI has changed, see the canonical docs link below)`,
|
|
5361
|
-
` 3. Toggle the feature on (and configure any required values)`,
|
|
5362
|
-
` 4. Refresh your dev server \u2014 no code changes required`,
|
|
5635
|
+
` 1. Run \`cometchat apply-feature ${feature.id}\` \u2014 the CLI flips the dashboard toggle via API.`,
|
|
5636
|
+
` 2. Refresh your dev server \u2014 no code changes required.`,
|
|
5363
5637
|
"",
|
|
5364
|
-
`
|
|
5365
|
-
`
|
|
5366
|
-
` For exact, up-to-date integration steps, query the cometchat-docs MCP for "${feature.id}".`
|
|
5638
|
+
` Manual fallback (if API isn't available): https://app.cometchat.com \u2192 ${feature.dashboard_path ?? "Extensions \u2192 " + feature.name}`,
|
|
5639
|
+
` \u{1F4D6} Canonical docs: ${docsUrl}`
|
|
5367
5640
|
);
|
|
5368
5641
|
} else {
|
|
5369
5642
|
lines.push(
|
|
5370
5643
|
`${feature.name} is a dashboard-toggle extension that ALSO requires explicit registration in your UIKitSettingsBuilder. The UI Kit only auto-attaches the 7 extensions in defaultExtensions[]; this one is not in that list.`,
|
|
5371
5644
|
"",
|
|
5372
5645
|
"Steps to enable:",
|
|
5373
|
-
` 1.
|
|
5374
|
-
` (dashboard hint \u2014 see canonical docs link below if the navigation has changed)`,
|
|
5646
|
+
` 1. Run \`cometchat apply-feature ${feature.id}\` \u2014 the CLI flips the dashboard toggle via API.`,
|
|
5375
5647
|
` 2. Register the extension via UIKitSettingsBuilder.setExtensions([...]) in your CometChat init.`,
|
|
5376
5648
|
` 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.`,
|
|
5377
|
-
` 3. Restart your dev server
|
|
5649
|
+
` 3. Restart your dev server.`,
|
|
5378
5650
|
"",
|
|
5379
5651
|
` \u{1F4D6} Canonical docs: ${docsUrl}`
|
|
5380
5652
|
);
|
|
5381
5653
|
}
|
|
5382
5654
|
return lines;
|
|
5383
5655
|
}
|
|
5656
|
+
case "ai-feature": {
|
|
5657
|
+
const docsUrl = feature.docs_topic ? `https://www.cometchat.com/docs/ui-kit/react/${feature.docs_topic}` : "https://www.cometchat.com/docs/ui-kit/react/ai-features";
|
|
5658
|
+
return [
|
|
5659
|
+
`${feature.name} is an AI feature that requires an OpenAI API key on the app's AI settings.`,
|
|
5660
|
+
"",
|
|
5661
|
+
"Steps to enable:",
|
|
5662
|
+
` 1. Run \`cometchat apply-feature ${feature.id} --openai-key sk-...\` \u2014 the CLI stores the key and flips the AI toggle via API.`,
|
|
5663
|
+
` 2. Refresh your dev server.`,
|
|
5664
|
+
"",
|
|
5665
|
+
` Get an OpenAI key: https://platform.openai.com/api-keys`,
|
|
5666
|
+
` \u{1F4D6} Canonical docs: ${docsUrl}`
|
|
5667
|
+
];
|
|
5668
|
+
}
|
|
5669
|
+
case "dashboard-only":
|
|
5670
|
+
return [
|
|
5671
|
+
`${feature.name} requires third-party config (API key, webhook, etc.) that only you can provide. The CLI cannot automate this.`,
|
|
5672
|
+
"",
|
|
5673
|
+
"Steps to enable:",
|
|
5674
|
+
` 1. Open https://app.cometchat.com \u2192 ${feature.dashboard_path ?? "Extensions \u2192 " + feature.name}`,
|
|
5675
|
+
` 2. Enter the required configuration values.`,
|
|
5676
|
+
` 3. Toggle the feature on; refresh your dev server.`,
|
|
5677
|
+
...feature.docs_topic ? ["", ` \u{1F4D6} Canonical docs: https://www.cometchat.com/docs/ui-kit/react/${feature.docs_topic}`] : []
|
|
5678
|
+
];
|
|
5384
5679
|
case "package-install": {
|
|
5385
5680
|
const pkg = rn && feature.package_native ? feature.package_native : feature.package;
|
|
5386
5681
|
const peers = rn ? feature.package_native_peers ?? [] : [];
|
|
@@ -5498,7 +5793,7 @@ async function featuresToggle(args, featureName, action) {
|
|
|
5498
5793
|
if (json) {
|
|
5499
5794
|
console.log(JSON.stringify({
|
|
5500
5795
|
...result2,
|
|
5501
|
-
available: catalog.features.filter((f) => f.type === "
|
|
5796
|
+
available: catalog.features.filter((f) => f.type === "extension").map((f) => f.id)
|
|
5502
5797
|
}, null, 2));
|
|
5503
5798
|
} else {
|
|
5504
5799
|
console.error(`\u2717 Feature "${featureName}" not found.`);
|
|
@@ -5506,11 +5801,12 @@ async function featuresToggle(args, featureName, action) {
|
|
|
5506
5801
|
}
|
|
5507
5802
|
return 1;
|
|
5508
5803
|
}
|
|
5509
|
-
if (match.type !== "
|
|
5804
|
+
if (match.type !== "extension") {
|
|
5805
|
+
const hint = match.type === "ai-feature" ? `Use \`cometchat apply-feature ${match.id} --openai-key <sk-\u2026>\` to enable AI features.` : `Run \`cometchat features info ${match.id}\` for the right instructions.`;
|
|
5510
5806
|
const result2 = {
|
|
5511
5807
|
status: "unsupported-type",
|
|
5512
5808
|
feature: match,
|
|
5513
|
-
error: `"${match.name}" is type "${match.type}" \u2014 not a dashboard toggle.
|
|
5809
|
+
error: `"${match.name}" is type "${match.type}" \u2014 not a dashboard toggle. ${hint}`
|
|
5514
5810
|
};
|
|
5515
5811
|
if (json) {
|
|
5516
5812
|
console.log(JSON.stringify(result2, null, 2));
|
|
@@ -5651,7 +5947,9 @@ function featuresList(args) {
|
|
|
5651
5947
|
}
|
|
5652
5948
|
const order = [
|
|
5653
5949
|
"default",
|
|
5654
|
-
"
|
|
5950
|
+
"extension",
|
|
5951
|
+
"ai-feature",
|
|
5952
|
+
"dashboard-only",
|
|
5655
5953
|
"package-install",
|
|
5656
5954
|
"component-swap"
|
|
5657
5955
|
];
|
|
@@ -6804,21 +7102,52 @@ init_state();
|
|
|
6804
7102
|
import { readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "node:fs";
|
|
6805
7103
|
import { join as join15, resolve as resolve16 } from "node:path";
|
|
6806
7104
|
var HELP15 = `
|
|
6807
|
-
cometchat apply-feature \u2014 apply a
|
|
7105
|
+
cometchat apply-feature \u2014 apply a feature on top of an existing integration
|
|
6808
7106
|
|
|
6809
7107
|
Usage:
|
|
6810
|
-
cometchat apply-feature <feature-id> [--path <p>] [--json]
|
|
7108
|
+
cometchat apply-feature <feature-id> [--app-id <id>] [--openai-key <k>] [--path <p>] [--json]
|
|
6811
7109
|
|
|
6812
7110
|
What this does:
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
7111
|
+
Dispatches by feature type from the catalog:
|
|
7112
|
+
extension Calls the dashboard API to flip the toggle. No code change.
|
|
7113
|
+
ai-feature Same, plus prompts/accepts an OpenAI key once per app.
|
|
7114
|
+
component-swap Walks files_owned and runs a word-boundary identifier
|
|
7115
|
+
swap (e.g. CometChatMessageComposer \u2192
|
|
7116
|
+
CometChatCompactMessageComposer). Updates checksums.
|
|
7117
|
+
default No-op \u2014 feature is already enabled by the UI Kit.
|
|
7118
|
+
dashboard-only Prints the dashboard path. Cannot automate (third-party
|
|
7119
|
+
keys, multi-field config).
|
|
7120
|
+
package-install Prints the npm install command. Cannot automate.
|
|
7121
|
+
|
|
7122
|
+
In stateful mode (default for web/RN where \`cometchat apply\` was run):
|
|
7123
|
+
- Reads .cometchat/state.json
|
|
7124
|
+
- Resolves the App ID from the project's env file
|
|
7125
|
+
- Records applied features in state.json for idempotency
|
|
7126
|
+
|
|
7127
|
+
In stateless mode (\`--app-id\` passed; for native iOS/Android/Flutter/Angular
|
|
7128
|
+
projects that don't go through \`cometchat apply\`):
|
|
7129
|
+
- Skips state.json
|
|
7130
|
+
- Uses the explicit --app-id value
|
|
7131
|
+
- Skips applied_features recording (server-side toggles are idempotent)
|
|
7132
|
+
- component-swap is unavailable (no files_owned to walk)
|
|
6817
7133
|
|
|
6818
7134
|
Flags:
|
|
6819
|
-
--
|
|
6820
|
-
|
|
6821
|
-
|
|
7135
|
+
--app-id <id> Stateless mode \u2014 toggle the feature on this app via the
|
|
7136
|
+
dashboard API without requiring \`cometchat apply\` to
|
|
7137
|
+
have been run first. Required for native cohorts
|
|
7138
|
+
(iOS / Android / Flutter / Angular).
|
|
7139
|
+
--openai-key <k> Pre-fill the OpenAI API key for ai-feature applies. If
|
|
7140
|
+
omitted, the command checks the app's existing AI
|
|
7141
|
+
settings; if no key is set, returns an error asking
|
|
7142
|
+
the user to pass --openai-key.
|
|
7143
|
+
--path <p> Project root (defaults to cwd).
|
|
7144
|
+
--json Machine-readable JSON output.
|
|
7145
|
+
--help, -h Show this help.
|
|
7146
|
+
|
|
7147
|
+
Auth:
|
|
7148
|
+
extension and ai-feature applies require a dashboard bearer token.
|
|
7149
|
+
Run \`cometchat auth login\` first if you haven't already. The token is
|
|
7150
|
+
stored in the OS keychain (or ~/.cometchat-cli/credentials.json fallback).
|
|
6822
7151
|
`;
|
|
6823
7152
|
function isJsonMode16(args) {
|
|
6824
7153
|
return args.flags.json === true || args.flags.json === "true";
|
|
@@ -6834,6 +7163,24 @@ function catalogPath2() {
|
|
|
6834
7163
|
function loadCatalog2() {
|
|
6835
7164
|
return JSON.parse(readFileSync12(catalogPath2(), "utf8"));
|
|
6836
7165
|
}
|
|
7166
|
+
function resolveAppId2(root, state2) {
|
|
7167
|
+
const envFileName = envFileNameFor(state2.framework);
|
|
7168
|
+
if (!envFileName) return null;
|
|
7169
|
+
const envPath = join15(root, envFileName);
|
|
7170
|
+
let content;
|
|
7171
|
+
try {
|
|
7172
|
+
content = readFileSync12(envPath, "utf8");
|
|
7173
|
+
} catch {
|
|
7174
|
+
return null;
|
|
7175
|
+
}
|
|
7176
|
+
const pairs = parseEnvPairs(content);
|
|
7177
|
+
const prefix = state2.env_var_prefix ?? "";
|
|
7178
|
+
const prefixed = pairs.get(`${prefix}COMETCHAT_APP_ID`);
|
|
7179
|
+
const bare = pairs.get("COMETCHAT_APP_ID");
|
|
7180
|
+
const value = (prefixed ?? bare ?? "").trim();
|
|
7181
|
+
if (value.length === 0 || isPlaceholder(value)) return null;
|
|
7182
|
+
return value;
|
|
7183
|
+
}
|
|
6837
7184
|
function swapIdentifier(content, from, to) {
|
|
6838
7185
|
const escaped = from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6839
7186
|
const re = new RegExp(`\\b${escaped}\\b`, "g");
|
|
@@ -6862,15 +7209,22 @@ async function applyFeature(args) {
|
|
|
6862
7209
|
result.error = "Usage: cometchat apply-feature <feature-id>";
|
|
6863
7210
|
return outputResult11(args, result, 1);
|
|
6864
7211
|
}
|
|
6865
|
-
|
|
7212
|
+
const appIdFlag = args.flags["app-id"];
|
|
7213
|
+
const explicitAppId = typeof appIdFlag === "string" ? appIdFlag.trim() : "";
|
|
7214
|
+
let state2 = null;
|
|
7215
|
+
let stateless = false;
|
|
7216
|
+
if (explicitAppId.length > 0) {
|
|
7217
|
+
stateless = true;
|
|
7218
|
+
} else if (hasState(root)) {
|
|
7219
|
+
state2 = readState(root);
|
|
7220
|
+
if (!state2) {
|
|
7221
|
+
result.status = "error";
|
|
7222
|
+
result.error = "state.json exists but could not be parsed (schema mismatch?)";
|
|
7223
|
+
return outputResult11(args, result, 1);
|
|
7224
|
+
}
|
|
7225
|
+
} else {
|
|
6866
7226
|
result.status = "no-integration";
|
|
6867
|
-
result.error = "No CometChat integration found. Run `cometchat apply` first.";
|
|
6868
|
-
return outputResult11(args, result, 1);
|
|
6869
|
-
}
|
|
6870
|
-
const state2 = readState(root);
|
|
6871
|
-
if (!state2) {
|
|
6872
|
-
result.status = "error";
|
|
6873
|
-
result.error = "state.json exists but could not be parsed (schema mismatch?)";
|
|
7227
|
+
result.error = "No CometChat integration found. Run `cometchat apply` first, or pass `--app-id <id>` for stateless API toggles (native cohorts: iOS/Android/Flutter/Angular).";
|
|
6874
7228
|
return outputResult11(args, result, 1);
|
|
6875
7229
|
}
|
|
6876
7230
|
let catalog;
|
|
@@ -6887,26 +7241,192 @@ async function applyFeature(args) {
|
|
|
6887
7241
|
result.error = `Feature "${featureId}" not in the catalog. Run \`cometchat features list\` to see available features.`;
|
|
6888
7242
|
return outputResult11(args, result, 1);
|
|
6889
7243
|
}
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
result.
|
|
7244
|
+
result.feature_type = feature.type;
|
|
7245
|
+
if (state2?.applied_features?.includes(featureId)) {
|
|
7246
|
+
result.status = "already-applied";
|
|
7247
|
+
result.next_steps = [`${feature.name} is already applied to this integration.`];
|
|
7248
|
+
return outputResult11(args, result, 0);
|
|
7249
|
+
}
|
|
7250
|
+
switch (feature.type) {
|
|
7251
|
+
case "default":
|
|
7252
|
+
return applyDefault(args, result, feature);
|
|
7253
|
+
case "component-swap":
|
|
7254
|
+
if (stateless || !state2) {
|
|
7255
|
+
result.status = "state-required";
|
|
7256
|
+
result.error = `Feature "${feature.id}" is type component-swap and requires \`cometchat apply\` to have run first (so we know which files to swap). \`--app-id\` mode does not support component-swap features.`;
|
|
7257
|
+
return outputResult11(args, result, 1);
|
|
7258
|
+
}
|
|
7259
|
+
return applyComponentSwap(args, result, root, state2, feature);
|
|
7260
|
+
case "extension":
|
|
7261
|
+
return applyExtensionToggle(args, result, root, state2, explicitAppId, feature);
|
|
7262
|
+
case "ai-feature":
|
|
7263
|
+
return applyAiFeatureToggle(args, result, root, state2, explicitAppId, feature);
|
|
7264
|
+
case "dashboard-only":
|
|
7265
|
+
return applyDashboardOnly(args, result, feature);
|
|
7266
|
+
case "package-install":
|
|
7267
|
+
return applyPackageInstall(args, result, feature);
|
|
7268
|
+
default: {
|
|
7269
|
+
result.status = "unsupported-feature-type";
|
|
7270
|
+
result.error = `Feature "${featureId}" has unknown type "${feature.type}".`;
|
|
7271
|
+
return outputResult11(args, result, 1);
|
|
7272
|
+
}
|
|
7273
|
+
}
|
|
7274
|
+
}
|
|
7275
|
+
function applyDefault(args, result, feature) {
|
|
7276
|
+
result.status = "already-applied";
|
|
7277
|
+
result.next_steps = [
|
|
7278
|
+
`${feature.name} is enabled by default in the UI Kit. No action needed.`,
|
|
7279
|
+
...feature.docs_topic ? [`Docs: https://www.cometchat.com/docs/ui-kit/react/${feature.docs_topic}`] : []
|
|
7280
|
+
];
|
|
7281
|
+
return outputResult11(args, result, 0);
|
|
7282
|
+
}
|
|
7283
|
+
async function applyExtensionToggle(args, result, root, state2, explicitAppId, feature) {
|
|
7284
|
+
const host = getApiHost();
|
|
7285
|
+
const creds = await loadToken(host);
|
|
7286
|
+
if (!creds) {
|
|
7287
|
+
result.status = "auth-required";
|
|
7288
|
+
result.error = "No dashboard bearer token found.";
|
|
6893
7289
|
result.next_steps = [
|
|
6894
|
-
|
|
7290
|
+
"Run `cometchat auth login` to authorize the CLI, then re-run this command."
|
|
6895
7291
|
];
|
|
6896
7292
|
return outputResult11(args, result, 1);
|
|
6897
7293
|
}
|
|
6898
|
-
|
|
7294
|
+
const appId = explicitAppId || (state2 ? resolveAppId2(root, state2) : null);
|
|
7295
|
+
if (!appId) {
|
|
6899
7296
|
result.status = "error";
|
|
6900
|
-
result.error =
|
|
7297
|
+
result.error = state2 ? "Could not read COMETCHAT_APP_ID from the project's env file." : "App ID is required. Pass `--app-id <id>`.";
|
|
7298
|
+
result.next_steps = state2 ? [`Confirm \`${state2.env_var_prefix ?? ""}COMETCHAT_APP_ID\` is set in your env file.`] : ["Run `cometchat apply-feature <id> --app-id <your-app-id>`."];
|
|
6901
7299
|
return outputResult11(args, result, 1);
|
|
6902
7300
|
}
|
|
6903
|
-
|
|
6904
|
-
|
|
7301
|
+
try {
|
|
7302
|
+
await toggleExtension(host, creds.token, appId, feature.id, "enable");
|
|
7303
|
+
} catch (err) {
|
|
7304
|
+
return handleApiError(args, result, err, feature);
|
|
7305
|
+
}
|
|
7306
|
+
if (state2) {
|
|
7307
|
+
state2.applied_features = [...state2.applied_features ?? [], feature.id];
|
|
7308
|
+
writeState(root, state2);
|
|
7309
|
+
appendAuditEntry(root, {
|
|
7310
|
+
command: `apply-feature ${feature.id}`,
|
|
7311
|
+
summary: `Enabled extension "${feature.name}" via dashboard API on app ${appId}`,
|
|
7312
|
+
inputs: { feature: feature.id, appId },
|
|
7313
|
+
decisions: {
|
|
7314
|
+
api_endpoint: `POST /apps/${appId}/extensions { enabled: ["${feature.id}"] }`,
|
|
7315
|
+
auto_wired: feature.auto_wired_in_uikit ? "yes" : "no"
|
|
7316
|
+
},
|
|
7317
|
+
files_modified: [],
|
|
7318
|
+
next_actions: [`Refresh the chat in the browser. ${feature.name} is now active.`]
|
|
7319
|
+
});
|
|
7320
|
+
}
|
|
7321
|
+
result.next_steps = [
|
|
7322
|
+
`Enabled ${feature.name} on app ${appId}.`,
|
|
7323
|
+
feature.auto_wired_in_uikit ? "The UI Kit's defaultExtensions[] picks this up automatically \u2014 refresh the browser." : "Refresh the chat in the browser. If the feature requires UIKitSettingsBuilder.setExtensions([...]), see the docs link below.",
|
|
7324
|
+
...feature.docs_topic ? [`Docs: https://www.cometchat.com/docs/ui-kit/react/${feature.docs_topic}`] : []
|
|
7325
|
+
];
|
|
7326
|
+
return outputResult11(args, result, 0);
|
|
7327
|
+
}
|
|
7328
|
+
async function applyAiFeatureToggle(args, result, root, state2, explicitAppId, feature) {
|
|
7329
|
+
if (!feature.ai_key) {
|
|
7330
|
+
result.status = "error";
|
|
7331
|
+
result.error = `Feature "${feature.id}" is type ai-feature but has no ai_key in the catalog.`;
|
|
7332
|
+
return outputResult11(args, result, 1);
|
|
7333
|
+
}
|
|
7334
|
+
const host = getApiHost();
|
|
7335
|
+
const creds = await loadToken(host);
|
|
7336
|
+
if (!creds) {
|
|
7337
|
+
result.status = "auth-required";
|
|
7338
|
+
result.error = "No dashboard bearer token found.";
|
|
6905
7339
|
result.next_steps = [
|
|
6906
|
-
|
|
6907
|
-
`Files using ${feature.swap_to}: scan with \`grep -r "${feature.swap_to}" .\`.`
|
|
7340
|
+
"Run `cometchat auth login` to authorize the CLI, then re-run this command."
|
|
6908
7341
|
];
|
|
6909
|
-
return outputResult11(args, result,
|
|
7342
|
+
return outputResult11(args, result, 1);
|
|
7343
|
+
}
|
|
7344
|
+
const appId = explicitAppId || (state2 ? resolveAppId2(root, state2) : null);
|
|
7345
|
+
if (!appId) {
|
|
7346
|
+
result.status = "error";
|
|
7347
|
+
result.error = state2 ? "Could not read COMETCHAT_APP_ID from the project's env file." : "App ID is required. Pass `--app-id <id>`.";
|
|
7348
|
+
result.next_steps = state2 ? [`Confirm \`${state2.env_var_prefix ?? ""}COMETCHAT_APP_ID\` is set in your env file.`] : ["Run `cometchat apply-feature <id> --app-id <your-app-id> --openai-key <sk-\u2026>`."];
|
|
7349
|
+
return outputResult11(args, result, 1);
|
|
7350
|
+
}
|
|
7351
|
+
const openaiFlag = args.flags["openai-key"];
|
|
7352
|
+
const explicitKey = typeof openaiFlag === "string" ? openaiFlag.trim() : "";
|
|
7353
|
+
let needsKeyWrite = false;
|
|
7354
|
+
if (explicitKey.length > 0) {
|
|
7355
|
+
needsKeyWrite = true;
|
|
7356
|
+
} else {
|
|
7357
|
+
let existing;
|
|
7358
|
+
try {
|
|
7359
|
+
existing = await getAiSettings(host, creds.token, appId);
|
|
7360
|
+
} catch (err) {
|
|
7361
|
+
return handleApiError(args, result, err, feature);
|
|
7362
|
+
}
|
|
7363
|
+
const currentKey = (existing?.openAIKey ?? "").trim();
|
|
7364
|
+
if (currentKey.length === 0) {
|
|
7365
|
+
result.status = "openai-key-required";
|
|
7366
|
+
result.error = `${feature.name} requires an OpenAI API key. None is set on app ${appId}.`;
|
|
7367
|
+
result.next_steps = [
|
|
7368
|
+
"Pass `--openai-key <sk-\u2026>` to set the key and enable the feature in one step.",
|
|
7369
|
+
"Get a key from https://platform.openai.com/api-keys"
|
|
7370
|
+
];
|
|
7371
|
+
return outputResult11(args, result, 1);
|
|
7372
|
+
}
|
|
7373
|
+
}
|
|
7374
|
+
try {
|
|
7375
|
+
if (needsKeyWrite) {
|
|
7376
|
+
await updateAiSettings(host, creds.token, appId, { openAIKey: explicitKey });
|
|
7377
|
+
}
|
|
7378
|
+
await toggleAiFeature(host, creds.token, appId, feature.ai_key, "enable");
|
|
7379
|
+
} catch (err) {
|
|
7380
|
+
return handleApiError(args, result, err, feature);
|
|
7381
|
+
}
|
|
7382
|
+
if (state2) {
|
|
7383
|
+
state2.applied_features = [...state2.applied_features ?? [], feature.id];
|
|
7384
|
+
writeState(root, state2);
|
|
7385
|
+
appendAuditEntry(root, {
|
|
7386
|
+
command: `apply-feature ${feature.id}`,
|
|
7387
|
+
summary: `Enabled AI feature "${feature.name}" via dashboard API on app ${appId}`,
|
|
7388
|
+
inputs: { feature: feature.id, appId, openai_key_written: needsKeyWrite },
|
|
7389
|
+
decisions: {
|
|
7390
|
+
settings_endpoint: needsKeyWrite ? `PUT /apps/${appId}/ai/settings { openAIKey: "(redacted)" }` : "skipped (key already set)",
|
|
7391
|
+
toggle_endpoint: `POST /apps/${appId}/features/ai.${feature.ai_key}/enabled`
|
|
7392
|
+
},
|
|
7393
|
+
files_modified: [],
|
|
7394
|
+
next_actions: [`Refresh the chat. AI ${feature.name} is now active.`]
|
|
7395
|
+
});
|
|
7396
|
+
}
|
|
7397
|
+
result.next_steps = [
|
|
7398
|
+
`Enabled ${feature.name} on app ${appId}.`,
|
|
7399
|
+
needsKeyWrite ? "OpenAI key stored on the app's AI settings." : "OpenAI key was already set.",
|
|
7400
|
+
"Refresh the chat in the browser to see the feature in action.",
|
|
7401
|
+
...feature.docs_topic ? [`Docs: https://www.cometchat.com/docs/ui-kit/react/${feature.docs_topic}`] : []
|
|
7402
|
+
];
|
|
7403
|
+
return outputResult11(args, result, 0);
|
|
7404
|
+
}
|
|
7405
|
+
function applyDashboardOnly(args, result, feature) {
|
|
7406
|
+
result.status = "manual-action-required";
|
|
7407
|
+
result.error = `${feature.name} requires config beyond a boolean toggle (third-party API key or multi-field setup).`;
|
|
7408
|
+
result.next_steps = [
|
|
7409
|
+
feature.dashboard_path ? `In the dashboard at https://app.cometchat.com \u2192 ${feature.dashboard_path}` : `Open https://app.cometchat.com and configure ${feature.name} on the Extensions page.`,
|
|
7410
|
+
...feature.docs_topic ? [`Docs: https://www.cometchat.com/docs/ui-kit/react/${feature.docs_topic}`] : []
|
|
7411
|
+
];
|
|
7412
|
+
return outputResult11(args, result, 1);
|
|
7413
|
+
}
|
|
7414
|
+
function applyPackageInstall(args, result, feature) {
|
|
7415
|
+
result.status = "manual-action-required";
|
|
7416
|
+
result.error = `${feature.name} requires installing an additional package.`;
|
|
7417
|
+
const pkg = feature.package ?? "the calls SDK";
|
|
7418
|
+
result.next_steps = [
|
|
7419
|
+
`Run \`npm install ${pkg}\` and restart your dev server.`,
|
|
7420
|
+
"The UI Kit's initiateAfterLogin() detects the SDK and wires up call buttons automatically.",
|
|
7421
|
+
...feature.docs_topic ? [`Docs: https://www.cometchat.com/docs/ui-kit/react/${feature.docs_topic}`] : []
|
|
7422
|
+
];
|
|
7423
|
+
return outputResult11(args, result, 1);
|
|
7424
|
+
}
|
|
7425
|
+
function applyComponentSwap(args, result, root, state2, feature) {
|
|
7426
|
+
if (!feature.swap_from || !feature.swap_to) {
|
|
7427
|
+
result.status = "error";
|
|
7428
|
+
result.error = `Feature "${feature.id}" is missing swap_from / swap_to in the catalog.`;
|
|
7429
|
+
return outputResult11(args, result, 1);
|
|
6910
7430
|
}
|
|
6911
7431
|
const newChecksums = { ...state2.checksums };
|
|
6912
7432
|
const modified = [];
|
|
@@ -6939,7 +7459,7 @@ async function applyFeature(args) {
|
|
|
6939
7459
|
const nextState = {
|
|
6940
7460
|
...state2,
|
|
6941
7461
|
checksums: newChecksums,
|
|
6942
|
-
applied_features: [...state2.applied_features ?? [],
|
|
7462
|
+
applied_features: [...state2.applied_features ?? [], feature.id]
|
|
6943
7463
|
};
|
|
6944
7464
|
writeState(root, nextState);
|
|
6945
7465
|
result.files_modified = modified;
|
|
@@ -6950,9 +7470,9 @@ async function applyFeature(args) {
|
|
|
6950
7470
|
...feature.docs_topic ? [`Docs: https://www.cometchat.com/docs/ui-kit/react/${feature.docs_topic}`] : []
|
|
6951
7471
|
];
|
|
6952
7472
|
appendAuditEntry(root, {
|
|
6953
|
-
command: `apply-feature ${
|
|
7473
|
+
command: `apply-feature ${feature.id}`,
|
|
6954
7474
|
summary: `Applied component-swap feature "${feature.name}" \u2014 replaced ${feature.swap_from} with ${feature.swap_to} in ${modified.length} owned file(s)`,
|
|
6955
|
-
inputs: { feature:
|
|
7475
|
+
inputs: { feature: feature.id },
|
|
6956
7476
|
decisions: {
|
|
6957
7477
|
swap_target: `${feature.swap_from} \u2192 ${feature.swap_to} (canonical drop-in variant from @cometchat/chat-uikit-react)`,
|
|
6958
7478
|
file_selection: `walked state.files_owned and ran a word-boundary regex replace; only files that contained the canonical token were touched`,
|
|
@@ -6963,6 +7483,24 @@ async function applyFeature(args) {
|
|
|
6963
7483
|
});
|
|
6964
7484
|
return outputResult11(args, result, 0);
|
|
6965
7485
|
}
|
|
7486
|
+
function handleApiError(args, result, err, feature) {
|
|
7487
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
7488
|
+
if (msg === "AUTH_FAILED") {
|
|
7489
|
+
result.status = "auth-required";
|
|
7490
|
+
result.error = "Bearer token rejected (expired or invalid).";
|
|
7491
|
+
result.next_steps = ["Run `cometchat auth login` to refresh the token, then re-run this command."];
|
|
7492
|
+
return outputResult11(args, result, 1);
|
|
7493
|
+
}
|
|
7494
|
+
if (msg === "NETWORK") {
|
|
7495
|
+
result.status = "error";
|
|
7496
|
+
result.error = "Network error reaching the dashboard API.";
|
|
7497
|
+
result.next_steps = ["Check your connection and re-run."];
|
|
7498
|
+
return outputResult11(args, result, 1);
|
|
7499
|
+
}
|
|
7500
|
+
result.status = "error";
|
|
7501
|
+
result.error = `Failed to enable ${feature.name}: ${msg}`;
|
|
7502
|
+
return outputResult11(args, result, 1);
|
|
7503
|
+
}
|
|
6966
7504
|
function outputResult11(args, result, exitCode) {
|
|
6967
7505
|
if (isJsonMode16(args)) {
|
|
6968
7506
|
console.log(JSON.stringify(enrichResultWithError(result), null, 2));
|
|
@@ -6976,9 +7514,11 @@ function printHumanReadable14(r) {
|
|
|
6976
7514
|
lines.push("");
|
|
6977
7515
|
if (r.status === "applied") {
|
|
6978
7516
|
lines.push(` \u2713 Applied feature: ${r.feature}`);
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
7517
|
+
if (r.files_modified.length > 0) {
|
|
7518
|
+
lines.push("");
|
|
7519
|
+
lines.push(" Files modified:");
|
|
7520
|
+
for (const f of r.files_modified) lines.push(` ${f}`);
|
|
7521
|
+
}
|
|
6982
7522
|
} else if (r.status === "already-applied") {
|
|
6983
7523
|
lines.push(` \u25E6 ${r.feature} is already applied \u2014 nothing to do.`);
|
|
6984
7524
|
} else {
|
|
@@ -7551,6 +8091,7 @@ Usage:
|
|
|
7551
8091
|
cometchat auth login [--token <bearer>] [--json]
|
|
7552
8092
|
cometchat auth signup [--json]
|
|
7553
8093
|
cometchat auth status [--json]
|
|
8094
|
+
cometchat auth me [--json]
|
|
7554
8095
|
cometchat auth logout [--json]
|
|
7555
8096
|
|
|
7556
8097
|
\`auth login\` and \`auth signup\` open your browser at the CometChat
|
|
@@ -7589,6 +8130,8 @@ async function auth(args) {
|
|
|
7589
8130
|
return authLogin(args);
|
|
7590
8131
|
case "status":
|
|
7591
8132
|
return authStatus(args);
|
|
8133
|
+
case "me":
|
|
8134
|
+
return authMe(args);
|
|
7592
8135
|
case "logout":
|
|
7593
8136
|
return authLogout(args);
|
|
7594
8137
|
case "signup":
|
|
@@ -7747,6 +8290,81 @@ async function authStatus(args) {
|
|
|
7747
8290
|
console.log("");
|
|
7748
8291
|
return 0;
|
|
7749
8292
|
}
|
|
8293
|
+
async function authMe(args) {
|
|
8294
|
+
const json = isJsonMode18(args);
|
|
8295
|
+
const apiHost = getApiHost();
|
|
8296
|
+
const creds = await loadToken(apiHost);
|
|
8297
|
+
if (!creds) {
|
|
8298
|
+
return printError(json, {
|
|
8299
|
+
status: "logged-out",
|
|
8300
|
+
error: "No bearer token found. Run `cometchat auth login` first."
|
|
8301
|
+
});
|
|
8302
|
+
}
|
|
8303
|
+
let user;
|
|
8304
|
+
try {
|
|
8305
|
+
user = await getCurrentUserWithLastApp(apiHost, creds.token);
|
|
8306
|
+
} catch (err) {
|
|
8307
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8308
|
+
if (msg === "AUTH_FAILED") {
|
|
8309
|
+
return printError(json, {
|
|
8310
|
+
status: "auth-required",
|
|
8311
|
+
error: "Bearer token rejected (expired or invalid).",
|
|
8312
|
+
next_step: "Run `cometchat auth login` to refresh the token."
|
|
8313
|
+
});
|
|
8314
|
+
}
|
|
8315
|
+
return printError(json, { status: "error", error: msg });
|
|
8316
|
+
}
|
|
8317
|
+
const meta = user.meta ?? {};
|
|
8318
|
+
const lastApp = user.last_app ?? null;
|
|
8319
|
+
const payload = {
|
|
8320
|
+
status: "logged-in",
|
|
8321
|
+
email: user.email ?? creds.email ?? null,
|
|
8322
|
+
name: user.name ?? null,
|
|
8323
|
+
role: typeof meta.role === "string" ? meta.role : null,
|
|
8324
|
+
other_role: typeof meta.otherRole === "string" && meta.otherRole.length > 0 ? meta.otherRole : null,
|
|
8325
|
+
intent: typeof meta.intent === "string" ? meta.intent : null,
|
|
8326
|
+
last_app: lastApp ? {
|
|
8327
|
+
id: lastApp.id,
|
|
8328
|
+
name: lastApp.name,
|
|
8329
|
+
region: lastApp.region,
|
|
8330
|
+
industry: lastApp.industry ?? null,
|
|
8331
|
+
technology: lastApp.technology ?? null,
|
|
8332
|
+
product: lastApp.product ?? null
|
|
8333
|
+
} : null
|
|
8334
|
+
};
|
|
8335
|
+
if (json) {
|
|
8336
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
8337
|
+
return 0;
|
|
8338
|
+
}
|
|
8339
|
+
console.log("");
|
|
8340
|
+
console.log(` \u2713 ${payload.email ?? "(no email on profile)"}`);
|
|
8341
|
+
if (payload.name) console.log(` Name: ${payload.name}`);
|
|
8342
|
+
console.log(` Role: ${payload.role ?? "(not set)"}${payload.other_role ? ` (${payload.other_role})` : ""}`);
|
|
8343
|
+
console.log(` Intent: ${payload.intent ?? "(not set)"}`);
|
|
8344
|
+
if (payload.last_app) {
|
|
8345
|
+
const la = payload.last_app;
|
|
8346
|
+
console.log(` Last app: ${la.name} (${la.region})`);
|
|
8347
|
+
if (la.industry || la.technology || la.product) {
|
|
8348
|
+
const bits = [
|
|
8349
|
+
la.industry && `industry=${la.industry}`,
|
|
8350
|
+
la.technology && `tech=${la.technology}`,
|
|
8351
|
+
la.product && `product=${la.product}`
|
|
8352
|
+
].filter(Boolean);
|
|
8353
|
+
console.log(` ${bits.join(", ")}`);
|
|
8354
|
+
}
|
|
8355
|
+
}
|
|
8356
|
+
console.log("");
|
|
8357
|
+
return 0;
|
|
8358
|
+
}
|
|
8359
|
+
function printError(json, payload) {
|
|
8360
|
+
if (json) {
|
|
8361
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
8362
|
+
} else {
|
|
8363
|
+
console.error(`\u2717 ${payload.error ?? payload.status}`);
|
|
8364
|
+
if (payload.next_step) console.error(` ${payload.next_step}`);
|
|
8365
|
+
}
|
|
8366
|
+
return 1;
|
|
8367
|
+
}
|
|
7750
8368
|
async function authLogout(args) {
|
|
7751
8369
|
const json = isJsonMode18(args);
|
|
7752
8370
|
const verbose = args.flags.verbose === true || args.flags.verbose === "true";
|
|
@@ -7934,7 +8552,7 @@ async function provisionSetup(args) {
|
|
|
7934
8552
|
return 1;
|
|
7935
8553
|
}
|
|
7936
8554
|
if (!frameworkFlag) {
|
|
7937
|
-
const msg = "Missing --framework. One of: reactjs, nextjs, react-router, astro, expo, react-native.";
|
|
8555
|
+
const msg = "Missing --framework. One of: reactjs, nextjs, react-router, astro, expo, react-native, angular, android, flutter, ios.";
|
|
7938
8556
|
if (json) return emitError(true, msg);
|
|
7939
8557
|
console.error(msg);
|
|
7940
8558
|
return 1;
|
|
@@ -8025,8 +8643,29 @@ var ENV_PREFIX_BY_FRAMEWORK = {
|
|
|
8025
8643
|
"react-router": "VITE_",
|
|
8026
8644
|
astro: "PUBLIC_",
|
|
8027
8645
|
expo: "EXPO_PUBLIC_",
|
|
8028
|
-
"react-native": ""
|
|
8646
|
+
"react-native": "",
|
|
8029
8647
|
// bare RN uses react-native-dotenv — no prefix
|
|
8648
|
+
angular: "",
|
|
8649
|
+
// Angular reads from src/environments/environment.ts; CLI writes
|
|
8650
|
+
// a no-prefix .env as a credentials handoff, and the skill
|
|
8651
|
+
// migrates the values into environment.ts during integration
|
|
8652
|
+
android: "",
|
|
8653
|
+
// Android reads from local.properties → BuildConfig at compile
|
|
8654
|
+
// time. CLI writes a no-prefix .env handoff; the skill teaches
|
|
8655
|
+
// the user to mirror values into local.properties + add
|
|
8656
|
+
// buildConfigField entries to app/build.gradle.
|
|
8657
|
+
flutter: "",
|
|
8658
|
+
// Flutter has no runtime .env. Credentials come from
|
|
8659
|
+
// --dart-define flags or a generated config Dart file. CLI
|
|
8660
|
+
// writes a no-prefix .env handoff; the skill teaches the
|
|
8661
|
+
// integration agent how to wire creds (typically a
|
|
8662
|
+
// const-class file + runner script with --dart-define).
|
|
8663
|
+
ios: ""
|
|
8664
|
+
// iOS has no runtime .env. Credentials live in a
|
|
8665
|
+
// Secrets.swift const file (gitignored) or get injected
|
|
8666
|
+
// via xcconfig build settings. CLI writes a no-prefix
|
|
8667
|
+
// .env handoff for the agent; the skill teaches the
|
|
8668
|
+
// Secrets.swift / xcconfig migration.
|
|
8030
8669
|
};
|
|
8031
8670
|
async function provisionList(args) {
|
|
8032
8671
|
const json = isJsonMode19(args);
|
|
@@ -5,7 +5,9 @@
|
|
|
5
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
|
+
"extension": "Pure boolean toggle on a backend extension. The CLI's `apply-feature` command calls POST /apps/{appId}/extensions to enable, no further config needed. Some have their UI decorator auto-attached by the UI Kit (auto_wired_in_uikit: true). Others additionally require passing the extension to UIKitSettingsBuilder.setExtensions([...]) before init.",
|
|
9
|
+
"ai-feature": "Backend AI feature toggle. Requires an OpenAI API key (PUT /apps/{appId}/ai/settings) before the feature can be enabled (POST /apps/{appId}/features/ai.{ai_key}/enabled). The CLI prompts for the key once, stores it on the app's settings, then enables the feature. The `ai_key` field is the API suffix.",
|
|
10
|
+
"dashboard-only": "Requires entering third-party config the user has to fetch themselves (Giphy/Stipop/Tenor API key, Chatwoot webhook URL, Intercom token, etc.) or making a multi-field configuration choice (Disappearing Messages interval, Message Shortcuts list). The CLI cannot automate these — falls back to opening the dashboard. `dashboard_path` is load-bearing for these entries.",
|
|
9
11
|
"package-install": "Requires installing an additional npm package. The UI Kit's initiateAfterLogin() auto-calls enableCalling() which detects the calls SDK and wires up call buttons. Currently calls only.",
|
|
10
12
|
"component-swap": "Requires replacing one UI Kit component with a drop-in variant. One-line change. The SDK currently ships exactly one such variant (CometChatCompactMessageComposer for rich text formatting); more variants will become catalog entries here as the SDK adds them."
|
|
11
13
|
},
|
|
@@ -126,7 +128,7 @@
|
|
|
126
128
|
{
|
|
127
129
|
"id": "bitly",
|
|
128
130
|
"name": "Bitly URL Shortening",
|
|
129
|
-
"type": "
|
|
131
|
+
"type": "extension",
|
|
130
132
|
"description": "Automatically shorten long URLs in messages using Bitly.",
|
|
131
133
|
"dashboard_path": "Extensions → Bitly → Toggle on",
|
|
132
134
|
"docs_topic": "extensions#bitly"
|
|
@@ -134,7 +136,7 @@
|
|
|
134
136
|
{
|
|
135
137
|
"id": "link-preview",
|
|
136
138
|
"name": "Link Preview",
|
|
137
|
-
"type": "
|
|
139
|
+
"type": "extension",
|
|
138
140
|
"auto_wired_in_uikit": true,
|
|
139
141
|
"description": "Show URL previews with title, description, and thumbnail. UI decorator auto-attached by the UI Kit (in defaultExtensions[]).",
|
|
140
142
|
"dashboard_path": "Extensions → Link Preview → Toggle on",
|
|
@@ -143,7 +145,7 @@
|
|
|
143
145
|
{
|
|
144
146
|
"id": "message-shortcuts",
|
|
145
147
|
"name": "Message Shortcuts",
|
|
146
|
-
"type": "dashboard-
|
|
148
|
+
"type": "dashboard-only",
|
|
147
149
|
"description": "Expand predefined short codes into full messages.",
|
|
148
150
|
"dashboard_path": "Extensions → Message Shortcuts → Configure shortcuts",
|
|
149
151
|
"docs_topic": "extensions#message-shortcuts"
|
|
@@ -151,7 +153,7 @@
|
|
|
151
153
|
{
|
|
152
154
|
"id": "pin-message",
|
|
153
155
|
"name": "Pin Message",
|
|
154
|
-
"type": "
|
|
156
|
+
"type": "extension",
|
|
155
157
|
"description": "Pin important messages for easy access.",
|
|
156
158
|
"dashboard_path": "Extensions → Pin Message → Toggle on",
|
|
157
159
|
"docs_topic": "extensions#pin-message"
|
|
@@ -159,7 +161,7 @@
|
|
|
159
161
|
{
|
|
160
162
|
"id": "rich-media-preview",
|
|
161
163
|
"name": "Rich Media Preview",
|
|
162
|
-
"type": "
|
|
164
|
+
"type": "extension",
|
|
163
165
|
"description": "Generate rich preview panels for URLs via iFramely.",
|
|
164
166
|
"dashboard_path": "Extensions → Rich Media Preview → Toggle on",
|
|
165
167
|
"docs_topic": "extensions#rich-media-preview"
|
|
@@ -167,7 +169,7 @@
|
|
|
167
169
|
{
|
|
168
170
|
"id": "save-message",
|
|
169
171
|
"name": "Save Message",
|
|
170
|
-
"type": "
|
|
172
|
+
"type": "extension",
|
|
171
173
|
"description": "Bookmark messages privately for later.",
|
|
172
174
|
"dashboard_path": "Extensions → Save Message → Toggle on",
|
|
173
175
|
"docs_topic": "extensions#save-message"
|
|
@@ -175,7 +177,7 @@
|
|
|
175
177
|
{
|
|
176
178
|
"id": "thumbnail-generation",
|
|
177
179
|
"name": "Thumbnail Generation",
|
|
178
|
-
"type": "
|
|
180
|
+
"type": "extension",
|
|
179
181
|
"auto_wired_in_uikit": true,
|
|
180
182
|
"description": "Auto-generate thumbnails for shared media to reduce bandwidth. UI decorator auto-attached by the UI Kit (in defaultExtensions[]).",
|
|
181
183
|
"dashboard_path": "Extensions → Thumbnail Generation → Toggle on",
|
|
@@ -184,7 +186,7 @@
|
|
|
184
186
|
{
|
|
185
187
|
"id": "tinyurl",
|
|
186
188
|
"name": "TinyURL Shortening",
|
|
187
|
-
"type": "
|
|
189
|
+
"type": "extension",
|
|
188
190
|
"description": "Shorten URLs using TinyURL service.",
|
|
189
191
|
"dashboard_path": "Extensions → TinyURL → Toggle on",
|
|
190
192
|
"docs_topic": "extensions#tinyurl"
|
|
@@ -192,7 +194,7 @@
|
|
|
192
194
|
{
|
|
193
195
|
"id": "voice-transcription",
|
|
194
196
|
"name": "Voice Transcription",
|
|
195
|
-
"type": "
|
|
197
|
+
"type": "extension",
|
|
196
198
|
"description": "Convert audio messages to text automatically.",
|
|
197
199
|
"dashboard_path": "Extensions → Voice Transcription → Toggle on",
|
|
198
200
|
"docs_topic": "extensions#voice-transcription"
|
|
@@ -200,7 +202,7 @@
|
|
|
200
202
|
{
|
|
201
203
|
"id": "giphy",
|
|
202
204
|
"name": "Giphy GIFs",
|
|
203
|
-
"type": "dashboard-
|
|
205
|
+
"type": "dashboard-only",
|
|
204
206
|
"description": "Search and share GIFs from Giphy.",
|
|
205
207
|
"dashboard_path": "Extensions → Giphy → Configure API key",
|
|
206
208
|
"docs_topic": "extensions#giphy"
|
|
@@ -208,7 +210,7 @@
|
|
|
208
210
|
{
|
|
209
211
|
"id": "message-translation",
|
|
210
212
|
"name": "Message Translation",
|
|
211
|
-
"type": "
|
|
213
|
+
"type": "extension",
|
|
212
214
|
"auto_wired_in_uikit": true,
|
|
213
215
|
"description": "Translate messages into the user's locale. UI decorator auto-attached by the UI Kit (in defaultExtensions[]).",
|
|
214
216
|
"dashboard_path": "Extensions → Message Translation → Toggle on",
|
|
@@ -217,7 +219,7 @@
|
|
|
217
219
|
{
|
|
218
220
|
"id": "polls",
|
|
219
221
|
"name": "Polls",
|
|
220
|
-
"type": "
|
|
222
|
+
"type": "extension",
|
|
221
223
|
"auto_wired_in_uikit": true,
|
|
222
224
|
"description": "Create polls in group discussions with preset answers. UI decorator auto-attached by the UI Kit (in defaultExtensions[]).",
|
|
223
225
|
"dashboard_path": "Extensions → Polls → Toggle on",
|
|
@@ -226,7 +228,7 @@
|
|
|
226
228
|
{
|
|
227
229
|
"id": "reminders",
|
|
228
230
|
"name": "Reminders",
|
|
229
|
-
"type": "
|
|
231
|
+
"type": "extension",
|
|
230
232
|
"description": "Set message reminders or personal notifications via bot.",
|
|
231
233
|
"dashboard_path": "Extensions → Reminders → Toggle on",
|
|
232
234
|
"docs_topic": "extensions#reminders"
|
|
@@ -234,7 +236,7 @@
|
|
|
234
236
|
{
|
|
235
237
|
"id": "stickers",
|
|
236
238
|
"name": "Stickers",
|
|
237
|
-
"type": "
|
|
239
|
+
"type": "extension",
|
|
238
240
|
"auto_wired_in_uikit": true,
|
|
239
241
|
"description": "Send pre-designed stickers in conversations. UI decorator auto-attached by the UI Kit (in defaultExtensions[]).",
|
|
240
242
|
"dashboard_path": "Extensions → Stickers → Toggle on",
|
|
@@ -243,7 +245,7 @@
|
|
|
243
245
|
{
|
|
244
246
|
"id": "stipop",
|
|
245
247
|
"name": "Stipop Sticker Library",
|
|
246
|
-
"type": "dashboard-
|
|
248
|
+
"type": "dashboard-only",
|
|
247
249
|
"description": "Integrates the Stipop sticker library.",
|
|
248
250
|
"dashboard_path": "Extensions → Stipop → Configure API key",
|
|
249
251
|
"docs_topic": "extensions#stipop"
|
|
@@ -251,7 +253,7 @@
|
|
|
251
253
|
{
|
|
252
254
|
"id": "tenor",
|
|
253
255
|
"name": "Tenor GIFs",
|
|
254
|
-
"type": "dashboard-
|
|
256
|
+
"type": "dashboard-only",
|
|
255
257
|
"description": "Search and share GIFs from Tenor.",
|
|
256
258
|
"dashboard_path": "Extensions → Tenor → Configure API key",
|
|
257
259
|
"docs_topic": "extensions#tenor"
|
|
@@ -259,7 +261,7 @@
|
|
|
259
261
|
{
|
|
260
262
|
"id": "collaborative-document",
|
|
261
263
|
"name": "Collaborative Document",
|
|
262
|
-
"type": "
|
|
264
|
+
"type": "extension",
|
|
263
265
|
"auto_wired_in_uikit": true,
|
|
264
266
|
"description": "Real-time shared document editing. UI decorator auto-attached by the UI Kit (in defaultExtensions[]).",
|
|
265
267
|
"dashboard_path": "Extensions → Collaborative Document → Toggle on",
|
|
@@ -268,7 +270,7 @@
|
|
|
268
270
|
{
|
|
269
271
|
"id": "collaborative-whiteboard",
|
|
270
272
|
"name": "Collaborative Whiteboard",
|
|
271
|
-
"type": "
|
|
273
|
+
"type": "extension",
|
|
272
274
|
"auto_wired_in_uikit": true,
|
|
273
275
|
"description": "Shared whiteboard for drawing and brainstorming. UI decorator auto-attached by the UI Kit (in defaultExtensions[]).",
|
|
274
276
|
"dashboard_path": "Extensions → Collaborative Whiteboard → Toggle on",
|
|
@@ -277,7 +279,7 @@
|
|
|
277
279
|
{
|
|
278
280
|
"id": "disappearing-messages",
|
|
279
281
|
"name": "Disappearing Messages",
|
|
280
|
-
"type": "dashboard-
|
|
282
|
+
"type": "dashboard-only",
|
|
281
283
|
"description": "Messages auto-delete after a specified interval.",
|
|
282
284
|
"dashboard_path": "Extensions → Disappearing Messages → Configure interval",
|
|
283
285
|
"docs_topic": "extensions#disappearing-messages"
|
|
@@ -285,7 +287,7 @@
|
|
|
285
287
|
{
|
|
286
288
|
"id": "chatwoot",
|
|
287
289
|
"name": "Chatwoot Integration",
|
|
288
|
-
"type": "dashboard-
|
|
290
|
+
"type": "dashboard-only",
|
|
289
291
|
"description": "Route messages to Chatwoot for customer support.",
|
|
290
292
|
"dashboard_path": "Extensions → Chatwoot → Configure webhook",
|
|
291
293
|
"docs_topic": "extensions#chatwoot"
|
|
@@ -293,7 +295,7 @@
|
|
|
293
295
|
{
|
|
294
296
|
"id": "intercom",
|
|
295
297
|
"name": "Intercom Integration",
|
|
296
|
-
"type": "dashboard-
|
|
298
|
+
"type": "dashboard-only",
|
|
297
299
|
"description": "Integrate Intercom for in-app customer support.",
|
|
298
300
|
"dashboard_path": "Extensions → Intercom → Configure API key",
|
|
299
301
|
"docs_topic": "extensions#intercom"
|
|
@@ -301,7 +303,9 @@
|
|
|
301
303
|
{
|
|
302
304
|
"id": "conversation-starter",
|
|
303
305
|
"name": "AI Conversation Starter",
|
|
304
|
-
"type": "
|
|
306
|
+
"type": "ai-feature",
|
|
307
|
+
"ai_key": "conversation-starter",
|
|
308
|
+
"requires": "openai-key",
|
|
305
309
|
"description": "AI-generated opening messages for new chats.",
|
|
306
310
|
"dashboard_path": "AI Features → Conversation Starter → Toggle on",
|
|
307
311
|
"docs_topic": "ai-features#conversation-starter"
|
|
@@ -309,7 +313,9 @@
|
|
|
309
313
|
{
|
|
310
314
|
"id": "smart-replies",
|
|
311
315
|
"name": "AI Smart Replies",
|
|
312
|
-
"type": "
|
|
316
|
+
"type": "ai-feature",
|
|
317
|
+
"ai_key": "smart-replies",
|
|
318
|
+
"requires": "openai-key",
|
|
313
319
|
"description": "AI-generated contextual response suggestions.",
|
|
314
320
|
"dashboard_path": "AI Features → Smart Replies → Toggle on",
|
|
315
321
|
"docs_topic": "ai-features#smart-replies"
|
|
@@ -317,7 +323,9 @@
|
|
|
317
323
|
{
|
|
318
324
|
"id": "conversation-summary",
|
|
319
325
|
"name": "AI Conversation Summary",
|
|
320
|
-
"type": "
|
|
326
|
+
"type": "ai-feature",
|
|
327
|
+
"ai_key": "conversation-summary",
|
|
328
|
+
"requires": "openai-key",
|
|
321
329
|
"description": "AI-generated recaps of extended conversations.",
|
|
322
330
|
"dashboard_path": "AI Features → Conversation Summary → Toggle on",
|
|
323
331
|
"docs_topic": "ai-features#conversation-summary"
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cometchat/skills-cli",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "CLI for the CometChat skills v3 architecture — auth, provision, detect, apply, verify CometChat integrations in React/Next.js/React-Router/Astro/Expo/React-Native projects.",
|
|
3
|
+
"version": "2.3.0",
|
|
4
|
+
"description": "CLI for the CometChat skills v3 architecture — auth, provision, detect, apply, verify CometChat integrations in React/Next.js/React-Router/Astro/Expo/React-Native/Angular/Android/iOS/Flutter projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"node": ">=18.0.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@types/node": "^
|
|
35
|
-
"esbuild": "^0.
|
|
34
|
+
"@types/node": "^25.6.0",
|
|
35
|
+
"esbuild": "^0.28.0",
|
|
36
36
|
"tsx": "^4.19.0",
|
|
37
37
|
"typescript": "^5.6.0"
|
|
38
38
|
},
|