@tavus/cvi-ui 0.0.1-beta.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.
Files changed (67) hide show
  1. package/.prettierrc.js +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +91 -0
  4. package/dev-components/components/cvi-provider/index.tsx +9 -0
  5. package/dev-components/hooks/README.md +499 -0
  6. package/dev-components/hooks/cvi-events-hooks.tsx +168 -0
  7. package/dev-components/hooks/use-cvi-call.tsx +24 -0
  8. package/dev-components/hooks/use-local-camera.tsx +20 -0
  9. package/dev-components/hooks/use-local-microphone.tsx +20 -0
  10. package/dev-components/hooks/use-local-screenshare.tsx +32 -0
  11. package/dev-components/hooks/use-remote-participant-ids.tsx +7 -0
  12. package/dev-components/hooks/use-replica-ids.tsx +9 -0
  13. package/dev-components/hooks/use-request-permissions.tsx +24 -0
  14. package/dev-components/hooks/use-start-haircheck.tsx +60 -0
  15. package/dist/index.js +237334 -0
  16. package/dist/types/cli/add.d.ts +20 -0
  17. package/dist/types/cli/info.d.ts +2 -0
  18. package/dist/types/cli/init.d.ts +23 -0
  19. package/dist/types/components/highlighter.d.ts +6 -0
  20. package/dist/types/components/logger.d.ts +8 -0
  21. package/dist/types/components/spinner.d.ts +4 -0
  22. package/dist/types/constants/components.d.ts +59 -0
  23. package/dist/types/constants/config.d.ts +5 -0
  24. package/dist/types/constants/errors.d.ts +5 -0
  25. package/dist/types/constants/frameworks.d.ts +39 -0
  26. package/dist/types/index.d.ts +1 -0
  27. package/dist/types/preflights/preflight-add.d.ts +15 -0
  28. package/dist/types/preflights/preflight-init.d.ts +9 -0
  29. package/dist/types/utils/add-components.d.ts +5 -0
  30. package/dist/types/utils/get-config.d.ts +51 -0
  31. package/dist/types/utils/get-package-info.d.ts +2 -0
  32. package/dist/types/utils/get-package-manager.d.ts +4 -0
  33. package/dist/types/utils/get-project-info.d.ts +10 -0
  34. package/dist/types/utils/handle-error.d.ts +1 -0
  35. package/dist/types/utils/resolve-components-tree.d.ts +4 -0
  36. package/dist/types/utils/resolve-import.d.ts +2 -0
  37. package/dist/types/utils/update-dependencies.d.ts +4 -0
  38. package/dist/types/utils/update-files.d.ts +14 -0
  39. package/dist/typescript-DhnEO4aV.js +12 -0
  40. package/dist/typescript-XxXP1Woc.js +14 -0
  41. package/eslint.config.js +12 -0
  42. package/package.json +67 -0
  43. package/prepare-scripts/convert-to-js.js +152 -0
  44. package/prepare-scripts/create-templates.js +265 -0
  45. package/rollup.config.js +28 -0
  46. package/src/cli/add.ts +124 -0
  47. package/src/cli/info.ts +21 -0
  48. package/src/cli/init.ts +131 -0
  49. package/src/components/highlighter.ts +8 -0
  50. package/src/components/logger.ts +22 -0
  51. package/src/components/spinner.ts +13 -0
  52. package/src/constants/config.ts +7 -0
  53. package/src/constants/errors.ts +5 -0
  54. package/src/constants/frameworks.ts +40 -0
  55. package/src/index.ts +26 -0
  56. package/src/preflights/preflight-add.ts +56 -0
  57. package/src/preflights/preflight-init.ts +77 -0
  58. package/src/utils/add-components.ts +52 -0
  59. package/src/utils/get-config.ts +60 -0
  60. package/src/utils/get-package-info.ts +14 -0
  61. package/src/utils/get-package-manager.ts +45 -0
  62. package/src/utils/get-project-info.ts +144 -0
  63. package/src/utils/handle-error.ts +34 -0
  64. package/src/utils/resolve-components-tree.ts +35 -0
  65. package/src/utils/update-dependencies.ts +37 -0
  66. package/src/utils/update-files.ts +212 -0
  67. package/tsconfig.json +23 -0
@@ -0,0 +1,56 @@
1
+ import path from 'node:path';
2
+ import { addOptionsSchema } from '@/src/cli/add';
3
+ import * as ERRORS from '@/src/constants/errors';
4
+ import { getConfig } from '@/src/utils/get-config';
5
+ import { highlighter } from '@/src/components/highlighter';
6
+ import { logger } from '@/src/components/logger';
7
+ import fs from 'fs-extra';
8
+ import { z } from 'zod';
9
+ import { CONFIG_NAME } from '../constants/config';
10
+
11
+ export async function preFlightAdd(options: z.infer<typeof addOptionsSchema>) {
12
+ const errors: Record<string, boolean> = {};
13
+ // Ensure target directory exists.
14
+ // Check for empty project. if no package.json exists, the project is empty.
15
+ if (
16
+ !fs.existsSync(options.cwd) ||
17
+ !fs.existsSync(path.resolve(options.cwd, 'package.json'))
18
+ ) {
19
+ errors[ERRORS.MISSING_DIR_OR_EMPTY_PROJECT] = true;
20
+ return {
21
+ errors,
22
+ config: null,
23
+ };
24
+ }
25
+
26
+ // Check for existing cvi-components.json file.
27
+ if (!fs.existsSync(path.resolve(options.cwd, CONFIG_NAME))) {
28
+ errors[ERRORS.MISSING_CONFIG] = true;
29
+ return {
30
+ errors,
31
+ config: null,
32
+ };
33
+ }
34
+
35
+ try {
36
+ const config = await getConfig(options.cwd);
37
+
38
+ return {
39
+ errors,
40
+ config: config!,
41
+ };
42
+ } catch {
43
+ logger.break();
44
+ logger.error(
45
+ `An invalid ${highlighter.info(
46
+ CONFIG_NAME
47
+ )} file was found at ${highlighter.info(
48
+ options.cwd
49
+ )}.\nBefore you can add components, you must create a valid ${highlighter.info(
50
+ CONFIG_NAME
51
+ )} file by running the ${highlighter.info('init')} command.`
52
+ );
53
+ logger.break();
54
+ process.exit(1);
55
+ }
56
+ }
@@ -0,0 +1,77 @@
1
+ import path from 'node:path';
2
+ import { initOptionsSchema } from '@/src/cli/init';
3
+ import * as ERRORS from '@/src/constants/errors';
4
+ import { getProjectInfo } from '@/src/utils/get-project-info';
5
+ import { highlighter } from '@/src/components/highlighter';
6
+ import { logger } from '@/src/components/logger';
7
+ import { spinner } from '@/src/components/spinner';
8
+ import fs from 'fs-extra';
9
+ import { z } from 'zod';
10
+ import { CONFIG_NAME } from '@/src/constants/config';
11
+
12
+ export async function preFlightInit(options: z.infer<typeof initOptionsSchema>) {
13
+ const errors: Record<string, boolean> = {};
14
+
15
+ // Ensure target directory exists.
16
+ // Check for empty project. We assume if no package.json exists, the project is empty.
17
+ if (!fs.existsSync(options.cwd) || !fs.existsSync(path.resolve(options.cwd, 'package.json'))) {
18
+ errors[ERRORS.MISSING_DIR_OR_EMPTY_PROJECT] = true;
19
+ return {
20
+ errors,
21
+ projectInfo: null,
22
+ };
23
+ }
24
+
25
+ const projectSpinner = spinner(`Preflight checks.`, {
26
+ silent: options.silent,
27
+ }).start();
28
+
29
+ if (fs.existsSync(path.resolve(options.cwd, CONFIG_NAME))) {
30
+ projectSpinner?.fail();
31
+ logger.break();
32
+ logger.error(
33
+ `A ${highlighter.info(CONFIG_NAME)} file already exists at ${highlighter.info(
34
+ options.cwd
35
+ )}.\nTo start over, remove the ${highlighter.info(
36
+ CONFIG_NAME
37
+ )} file and run ${highlighter.info('init')} again.`
38
+ );
39
+ logger.break();
40
+ process.exit(1);
41
+ }
42
+
43
+ projectSpinner?.succeed();
44
+
45
+ const frameworkSpinner = spinner(`Verifying framework.`, {
46
+ silent: options.silent,
47
+ }).start();
48
+ const projectInfo = await getProjectInfo(options.cwd);
49
+ if (!projectInfo || projectInfo?.framework.name === 'manual') {
50
+ errors[ERRORS.UNSUPPORTED_FRAMEWORK] = true;
51
+ frameworkSpinner?.fail();
52
+ logger.break();
53
+
54
+ logger.error(
55
+ `We could not detect a supported framework at ${highlighter.info(options.cwd)}.\n` +
56
+ `Visit ${highlighter.info(
57
+ 'https://github.com/Tavus-Engineering/tavus-cvi'
58
+ )} to manually configure your project.\nOnce configured, you can use the cli to add components.`
59
+ );
60
+
61
+ logger.break();
62
+ process.exit(1);
63
+ }
64
+ frameworkSpinner?.succeed(
65
+ `Verifying framework. Found ${highlighter.info(projectInfo.framework.label)}.`
66
+ );
67
+
68
+ if (Object.keys(errors).length > 0) {
69
+ logger.break();
70
+ process.exit(1);
71
+ }
72
+
73
+ return {
74
+ errors,
75
+ projectInfo,
76
+ };
77
+ }
@@ -0,0 +1,52 @@
1
+ import { configSchema, type Config } from '@/src/utils/get-config';
2
+ import { handleError } from '@/src/utils/handle-error';
3
+ import { spinner } from '@/src/components/spinner';
4
+ import { updateDependencies } from '@/src/utils/update-dependencies';
5
+ import { updateFiles } from '@/src/utils/update-files';
6
+ import { resolveComponentsTree } from '@/src/utils/resolve-components-tree';
7
+ import { z } from 'zod';
8
+
9
+ export async function addComponents(
10
+ components: string[],
11
+ config: Config,
12
+ options: {
13
+ overwrite?: boolean;
14
+ silent?: boolean;
15
+ }
16
+ ) {
17
+ options = {
18
+ overwrite: false,
19
+ silent: false,
20
+ ...options,
21
+ };
22
+
23
+ return await addProjectComponents(components, config, options);
24
+ }
25
+
26
+ async function addProjectComponents(
27
+ components: string[],
28
+ config: z.infer<typeof configSchema>,
29
+ options: {
30
+ overwrite?: boolean;
31
+ silent?: boolean;
32
+ }
33
+ ) {
34
+ const registrySpinner = spinner(`Checking components.`, {
35
+ silent: options.silent,
36
+ })?.start();
37
+ const tree = resolveComponentsTree(components);
38
+ if (!tree) {
39
+ registrySpinner?.fail();
40
+ return handleError(new Error('Failed to fetch components.'));
41
+ }
42
+ registrySpinner?.succeed();
43
+
44
+ await updateDependencies(tree.dependencies, config, {
45
+ silent: options.silent,
46
+ });
47
+
48
+ await updateFiles(tree.componentsDependencies, config, {
49
+ overwrite: options.overwrite,
50
+ silent: options.silent,
51
+ });
52
+ }
@@ -0,0 +1,60 @@
1
+ import { highlighter } from '@/src/components/highlighter';
2
+ import { cosmiconfig } from 'cosmiconfig';
3
+ import { z } from 'zod';
4
+ import { CONFIG_NAME } from '@/src/constants/config';
5
+
6
+ const explorer = cosmiconfig('components', {
7
+ searchPlaces: [CONFIG_NAME],
8
+ });
9
+
10
+ export const rawConfigSchema = z
11
+ .object({
12
+ tsx: z.coerce.boolean().default(true),
13
+ })
14
+ .strict();
15
+
16
+ export type RawConfig = z.infer<typeof rawConfigSchema>;
17
+
18
+ export const configSchema = rawConfigSchema.extend({
19
+ resolvedPaths: z.object({
20
+ cwd: z.string(),
21
+ components: z.string(),
22
+ }),
23
+ });
24
+
25
+ export type Config = z.infer<typeof configSchema>;
26
+
27
+ export async function getConfig(cwd: string) {
28
+ const config = await getRawConfig(cwd);
29
+
30
+ if (!config) {
31
+ return null;
32
+ }
33
+
34
+ return await resolveConfigPaths(cwd, config);
35
+ }
36
+
37
+ export async function resolveConfigPaths(cwd: string, config: RawConfig) {
38
+ return configSchema.parse({
39
+ ...config,
40
+ resolvedPaths: {
41
+ cwd,
42
+ // TODO: make this configurable
43
+ components: '/src/components',
44
+ },
45
+ });
46
+ }
47
+
48
+ export async function getRawConfig(cwd: string): Promise<RawConfig | null> {
49
+ try {
50
+ const configResult = await explorer.search(cwd);
51
+ if (!configResult) {
52
+ return null;
53
+ }
54
+
55
+ return rawConfigSchema.parse(configResult.config);
56
+ } catch {
57
+ const componentPath = `${cwd}/${CONFIG_NAME}`;
58
+ throw new Error(`Invalid configuration found in ${highlighter.info(componentPath)}.`);
59
+ }
60
+ }
@@ -0,0 +1,14 @@
1
+ import path from "node:path"
2
+ import fs from "fs-extra"
3
+ import { type PackageJson } from "type-fest"
4
+
5
+ export function getPackageInfo(
6
+ cwd: string = "",
7
+ shouldThrow: boolean = true
8
+ ): PackageJson | null {
9
+ const packageJsonPath = path.join(cwd, "package.json")
10
+
11
+ return fs.readJSONSync(packageJsonPath, {
12
+ throws: shouldThrow,
13
+ }) as PackageJson
14
+ }
@@ -0,0 +1,45 @@
1
+ import { detect } from "@antfu/ni"
2
+
3
+ export async function getPackageManager(
4
+ targetDir: string,
5
+ { withFallback }: { withFallback?: boolean } = {
6
+ withFallback: false,
7
+ }
8
+ ): Promise<"yarn" | "pnpm" | "bun" | "npm" | "deno"> {
9
+ const packageManager = await detect({ programmatic: true, cwd: targetDir })
10
+
11
+ if (packageManager === "yarn@berry") return "yarn"
12
+ if (packageManager === "pnpm@6") return "pnpm"
13
+ if (packageManager === "bun") return "bun"
14
+ if (packageManager === "deno") return "deno"
15
+ if (!withFallback) {
16
+ return packageManager ?? "npm"
17
+ }
18
+
19
+ // Fallback to user agent if not detected.
20
+ const userAgent = process.env.npm_config_user_agent || ""
21
+
22
+ if (userAgent.startsWith("yarn")) {
23
+ return "yarn"
24
+ }
25
+
26
+ if (userAgent.startsWith("pnpm")) {
27
+ return "pnpm"
28
+ }
29
+
30
+ if (userAgent.startsWith("bun")) {
31
+ return "bun"
32
+ }
33
+
34
+ return "npm"
35
+ }
36
+
37
+ export async function getPackageRunner(cwd: string) {
38
+ const packageManager = await getPackageManager(cwd)
39
+
40
+ if (packageManager === "pnpm") return "pnpm dlx"
41
+
42
+ if (packageManager === "bun") return "bunx"
43
+
44
+ return "npx"
45
+ }
@@ -0,0 +1,144 @@
1
+ import path from 'node:path';
2
+ import { FRAMEWORKS, Framework } from '@/src/constants/frameworks';
3
+ import {
4
+ Config,
5
+ RawConfig,
6
+ getConfig,
7
+ resolveConfigPaths,
8
+ } from '@/src/utils/get-config';
9
+ import { getPackageInfo } from '@/src/utils/get-package-info';
10
+ import fg from 'fast-glob';
11
+ import fs from 'fs-extra';
12
+
13
+ export type ProjectInfo = {
14
+ framework: Framework;
15
+ isTsx: boolean;
16
+ isSrcDir: boolean;
17
+ };
18
+
19
+ const PROJECT_SHARED_IGNORE = [
20
+ '**/node_modules/**',
21
+ '.next',
22
+ 'public',
23
+ 'dist',
24
+ 'build',
25
+ ];
26
+
27
+ export async function getProjectInfo(cwd: string): Promise<ProjectInfo | null> {
28
+ const [configFiles, isSrcDir, isTsx, packageJson] = await Promise.all([
29
+ fg.glob(
30
+ '**/{next,vite,astro,app}.config.*|gatsby-config.*|composer.json|react-router.config.*',
31
+ {
32
+ cwd,
33
+ deep: 3,
34
+ ignore: PROJECT_SHARED_IGNORE,
35
+ }
36
+ ),
37
+ fs.pathExists(path.resolve(cwd, 'src')),
38
+ isTypeScriptProject(cwd),
39
+ getPackageInfo(cwd, false),
40
+ ]);
41
+
42
+ const isUsingAppDir = await fs.pathExists(
43
+ path.resolve(cwd, `${isSrcDir ? 'src/' : ''}app`)
44
+ );
45
+
46
+ const type: ProjectInfo = {
47
+ framework: FRAMEWORKS['manual'],
48
+ isTsx,
49
+ isSrcDir,
50
+ };
51
+
52
+ // Next.js.
53
+ if (configFiles.find((file) => file.startsWith('next.config.'))?.length) {
54
+ type.framework = isUsingAppDir
55
+ ? FRAMEWORKS['next-app']
56
+ : FRAMEWORKS['next-pages'];
57
+ return type;
58
+ }
59
+
60
+ // Astro.
61
+ if (configFiles.find((file) => file.startsWith('astro.config.'))?.length) {
62
+ type.framework = FRAMEWORKS['astro'];
63
+ return type;
64
+ }
65
+
66
+ // Gatsby.
67
+ if (configFiles.find((file) => file.startsWith('gatsby-config.'))?.length) {
68
+ type.framework = FRAMEWORKS['gatsby'];
69
+ return type;
70
+ }
71
+
72
+ // Remix.
73
+ if (
74
+ Object.keys(packageJson?.dependencies ?? {}).find((dep) =>
75
+ dep.startsWith('@remix-run/')
76
+ )
77
+ ) {
78
+ type.framework = FRAMEWORKS['remix'];
79
+ return type;
80
+ }
81
+
82
+ // TanStack Start.
83
+ if (
84
+ configFiles.find((file) => file.startsWith('app.config.'))?.length &&
85
+ [
86
+ ...Object.keys(packageJson?.dependencies ?? {}),
87
+ ...Object.keys(packageJson?.devDependencies ?? {}),
88
+ ].find((dep) => dep.startsWith('@tanstack/start'))
89
+ ) {
90
+ type.framework = FRAMEWORKS['tanstack-start'];
91
+ return type;
92
+ }
93
+
94
+ // React Router.
95
+ if (
96
+ configFiles.find((file) => file.startsWith('react-router.config.'))?.length
97
+ ) {
98
+ type.framework = FRAMEWORKS['react-router'];
99
+ return type;
100
+ }
101
+
102
+ // Vite.
103
+ // Some Remix templates also have a vite.config.* file.
104
+ // We'll assume that it got caught by the Remix check above.
105
+ if (configFiles.find((file) => file.startsWith('vite.config.'))?.length) {
106
+ type.framework = FRAMEWORKS['vite'];
107
+ return type;
108
+ }
109
+
110
+ return type;
111
+ }
112
+
113
+ export async function isTypeScriptProject(cwd: string) {
114
+ const files = await fg.glob('tsconfig.*', {
115
+ cwd,
116
+ deep: 1,
117
+ ignore: PROJECT_SHARED_IGNORE,
118
+ });
119
+
120
+ return files.length > 0;
121
+ }
122
+
123
+ export async function getProjectConfig(
124
+ cwd: string,
125
+ defaultProjectInfo: ProjectInfo | null = null
126
+ ): Promise<Config | null> {
127
+ // Check for existing component config.
128
+ const [existingConfig, projectInfo] = await Promise.all([
129
+ getConfig(cwd),
130
+ !defaultProjectInfo
131
+ ? getProjectInfo(cwd)
132
+ : Promise.resolve(defaultProjectInfo),
133
+ ]);
134
+
135
+ if (existingConfig) {
136
+ return existingConfig;
137
+ }
138
+
139
+ const config: RawConfig = {
140
+ tsx: !!projectInfo?.isTsx,
141
+ };
142
+
143
+ return await resolveConfigPaths(cwd, config);
144
+ }
@@ -0,0 +1,34 @@
1
+ import { highlighter } from '@/src/components/highlighter';
2
+ import { logger } from '@/src/components/logger';
3
+ import { z } from 'zod';
4
+
5
+ export function handleError(error: unknown) {
6
+ logger.error(
7
+ `Something went wrong. Please check the error below for more details.`
8
+ );
9
+ logger.error(`If the problem persists, please open an issue on GitHub.`);
10
+ logger.error('');
11
+ if (typeof error === 'string') {
12
+ logger.error(error);
13
+ logger.break();
14
+ process.exit(1);
15
+ }
16
+
17
+ if (error instanceof z.ZodError) {
18
+ logger.error('Validation failed:');
19
+ for (const [key, value] of Object.entries(error.flatten().fieldErrors)) {
20
+ logger.error(`- ${highlighter.info(key)}: ${value}`);
21
+ }
22
+ logger.break();
23
+ process.exit(1);
24
+ }
25
+
26
+ if (error instanceof Error) {
27
+ logger.error(error.message);
28
+ logger.break();
29
+ process.exit(1);
30
+ }
31
+
32
+ logger.break();
33
+ process.exit(1);
34
+ }
@@ -0,0 +1,35 @@
1
+ import * as templates from '../templates';
2
+
3
+ type ComponentKey = keyof typeof templates.tsx;
4
+
5
+ export function resolveComponentsTree(components: string[]) {
6
+ // npm libraries which should be added to the project
7
+ const dependencies = new Set<string>();
8
+ // components which should be added in addition to the component
9
+ const componentsDependencies = new Set<string>([...components]);
10
+
11
+ // Process each component and its dependencies recursively
12
+ function processComponent(componentName: string) {
13
+ const component = templates.tsx[componentName as ComponentKey];
14
+ if (!component) return;
15
+
16
+ // Add component's dependencies
17
+ (component as { dependencies?: string[] }).dependencies?.forEach((dep: string) => dependencies.add(dep));
18
+
19
+ // Process component's component dependencies recursively
20
+ (component as { componentsDependencies?: string[] }).componentsDependencies?.forEach((dep: string) => {
21
+ componentsDependencies.add(dep);
22
+ processComponent(dep);
23
+ });
24
+ }
25
+
26
+ // Process all requested components
27
+ components.forEach((componentName) => {
28
+ processComponent(componentName);
29
+ });
30
+
31
+ return {
32
+ dependencies: Array.from(dependencies),
33
+ componentsDependencies: Array.from(componentsDependencies),
34
+ };
35
+ }
@@ -0,0 +1,37 @@
1
+ import { Config } from '@/src/utils/get-config';
2
+ import { getPackageManager } from '@/src/utils/get-package-manager';
3
+ import { spinner } from '@/src/components/spinner';
4
+ import { execa } from 'execa';
5
+
6
+ export async function updateDependencies(
7
+ dependencies: string[],
8
+ config: Config,
9
+ options: {
10
+ silent?: boolean;
11
+ }
12
+ ) {
13
+ if (!dependencies?.length) {
14
+ return;
15
+ }
16
+
17
+ options = {
18
+ silent: false,
19
+ ...options,
20
+ };
21
+
22
+ const dependenciesSpinner = spinner(`Installing dependencies.`, {
23
+ silent: options.silent,
24
+ })?.start();
25
+ const packageManager = await getPackageManager(config.resolvedPaths.cwd);
26
+ dependenciesSpinner?.start();
27
+
28
+ await execa(
29
+ packageManager,
30
+ [packageManager === 'npm' ? 'install' : 'add', ...dependencies],
31
+ {
32
+ cwd: config.resolvedPaths.cwd,
33
+ }
34
+ );
35
+
36
+ dependenciesSpinner?.succeed();
37
+ }