Full Code of FlowTestAI/FlowTest for AI

main 3b9ca3a49b89 cached
217 files
635.3 KB
150.5k tokens
211 symbols
1 requests
Download .txt
Showing preview only (695K chars total). Download the full file or copy to clipboard to get everything.
Repository: FlowTestAI/FlowTest
Branch: main
Commit: 3b9ca3a49b89
Files: 217
Total size: 635.3 KB

Directory structure:
gitextract_hj9fjt_a/

├── .changeset/
│   ├── README.md
│   ├── config.json
│   ├── early-colts-approve.md
│   ├── honest-bags-fail.md
│   └── proud-ants-flash.md
├── .eslintignore
├── .eslintrc.js
├── .github/
│   └── CODEOWNERS
├── .gitignore
├── .npmrc
├── .prettierrc
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── jsconfig.json
├── package.json
├── packages/
│   ├── flowtest-cli/
│   │   ├── LICENSE.md
│   │   ├── README.md
│   │   ├── bin/
│   │   │   ├── axiosClient.js
│   │   │   └── index.js
│   │   ├── graph/
│   │   │   ├── Graph.js
│   │   │   ├── GraphLogger.js
│   │   │   ├── compute/
│   │   │   │   ├── assertnode.js
│   │   │   │   ├── authnode.js
│   │   │   │   ├── node.js
│   │   │   │   ├── requestnode.js
│   │   │   │   ├── setvarnode.js
│   │   │   │   ├── utils.js
│   │   │   │   └── utils.test.js
│   │   │   └── constants/
│   │   │       ├── assertOperators.js
│   │   │       └── evaluateOperators.js
│   │   ├── package.json
│   │   └── utils/
│   │       ├── flowparser/
│   │       │   ├── AssertNode.js
│   │       │   ├── AuthNode.js
│   │       │   ├── DelayNode.js
│   │       │   ├── NestedFlowNode.js
│   │       │   ├── Node.js
│   │       │   ├── OutputNode.js
│   │       │   ├── RequestNode.js
│   │       │   ├── SetVarNode.js
│   │       │   ├── StartNode.js
│   │       │   └── parser.js
│   │       └── readfile.js
│   └── flowtest-electron/
│       ├── .npmrc
│       ├── CHANGELOG.md
│       ├── assets/
│       │   └── MyIcon.icns
│       ├── electron-main.js
│       ├── electron-menu.js
│       ├── notarize.js
│       ├── package.json
│       ├── preload.js
│       ├── src/
│       │   ├── ai/
│       │   │   ├── flowtestai.js
│       │   │   └── models/
│       │   │       ├── bedrock_claude.js
│       │   │       ├── gemini.js
│       │   │       └── openai.js
│       │   ├── app/
│       │   │   └── watcher.js
│       │   ├── ipc/
│       │   │   ├── axiosClient.js
│       │   │   ├── collection.js
│       │   │   └── settings.js
│       │   ├── store/
│       │   │   ├── collection.js
│       │   │   └── settings.js
│       │   └── utils/
│       │       ├── collection.js
│       │       ├── collection.test.js
│       │       ├── filemanager/
│       │       │   ├── createdirectory.js
│       │       │   ├── createfile.js
│       │       │   ├── deletedirectory.js
│       │       │   ├── deletefile.js
│       │       │   ├── filesystem.js
│       │       │   ├── readfile.js
│       │       │   └── updatefile.js
│       │       ├── flowparser/
│       │       │   ├── AssertNode.js
│       │       │   ├── AuthNode.js
│       │       │   ├── DelayNode.js
│       │       │   ├── NestedFlowNode.js
│       │       │   ├── Node.js
│       │       │   ├── OutputNode.js
│       │       │   ├── RequestNode.js
│       │       │   ├── SetVarNode.js
│       │       │   ├── StartNode.js
│       │       │   └── parser.js
│       │       ├── generate-request-body.js
│       │       └── generate-request-parameters.js
│       └── tests/
│           ├── store/
│           │   ├── collection-store.test.js
│           │   └── settings-store.test.js
│           ├── test.yaml
│           ├── utils/
│           │   ├── collection-parser.test.js
│           │   ├── filemanager.test.js
│           │   ├── flowtest-ai.test.js
│           │   └── flowtest-parser.test.js
│           └── watcher.test.js
├── pnpm-workspace.yaml
├── postcss.config.js
├── public/
│   ├── index.html
│   ├── manifest.json
│   └── robots.txt
├── src/
│   ├── App.css
│   ├── App.js
│   ├── App.test.js
│   ├── components/
│   │   ├── atoms/
│   │   │   ├── EditableTextItem.js
│   │   │   ├── Editor.js
│   │   │   ├── Logo.js
│   │   │   ├── SelectAuthKeys.js
│   │   │   ├── SelectEnvironment.js
│   │   │   ├── Tabs.js
│   │   │   ├── ThemeController.js
│   │   │   ├── common/
│   │   │   │   ├── Button.js
│   │   │   │   ├── HomeLoadingScreen.js
│   │   │   │   ├── HorizontalDivider.js
│   │   │   │   ├── LoadingSpinner.js
│   │   │   │   ├── NumberInput.js
│   │   │   │   ├── TextEditor.js
│   │   │   │   ├── TextInput.js
│   │   │   │   ├── TextInputWithLabel.js
│   │   │   │   └── TimeoutSelector.js
│   │   │   ├── flow/
│   │   │   │   ├── FlowNode.js
│   │   │   │   ├── NodeHorizontalDivider.js
│   │   │   │   └── Textarea.js
│   │   │   ├── sidebar/
│   │   │   │   ├── collections/
│   │   │   │   │   └── OptionsMenu.js
│   │   │   │   └── environments/
│   │   │   │       └── EnvOptionsMenu.js
│   │   │   └── util.js
│   │   ├── layouts/
│   │   │   ├── SplitPane.js
│   │   │   └── WithoutSplitPane.js
│   │   ├── molecules/
│   │   │   ├── environment/
│   │   │   │   └── index.js
│   │   │   ├── flow/
│   │   │   │   ├── AddNodes.js
│   │   │   │   ├── constants/
│   │   │   │   │   ├── assertOperators.js
│   │   │   │   │   ├── evaluateOperators.js
│   │   │   │   │   └── requestNodes.js
│   │   │   │   ├── edges/
│   │   │   │   │   └── ButtonEdge.js
│   │   │   │   ├── flowtestai.js
│   │   │   │   ├── graph/
│   │   │   │   │   ├── Graph.js
│   │   │   │   │   ├── GraphLogger.js
│   │   │   │   │   ├── GraphRun.js
│   │   │   │   │   └── compute/
│   │   │   │   │       ├── assertnode.js
│   │   │   │   │       ├── authnode.js
│   │   │   │   │       ├── nestedflownode.js
│   │   │   │   │       ├── node.js
│   │   │   │   │       ├── requestnode.js
│   │   │   │   │       ├── setvarnode.js
│   │   │   │   │       ├── utils.js
│   │   │   │   │       └── utils.test.js
│   │   │   │   ├── index.js
│   │   │   │   ├── nodes/
│   │   │   │   │   ├── AssertNode.js
│   │   │   │   │   ├── AuthNode.js
│   │   │   │   │   ├── DelayNode.js
│   │   │   │   │   ├── FormDataSelector.js
│   │   │   │   │   ├── NestedFlowNode.js
│   │   │   │   │   ├── OutputNode.js
│   │   │   │   │   ├── RequestBody.js
│   │   │   │   │   ├── RequestNode.js
│   │   │   │   │   └── SetVarNode.js
│   │   │   │   └── utils.js
│   │   │   ├── footers/
│   │   │   │   └── MainFooter.js
│   │   │   ├── headers/
│   │   │   │   ├── MainHeader.js
│   │   │   │   ├── SideBarHeader.js
│   │   │   │   ├── SideBarSubHeader.js
│   │   │   │   ├── TabPanelHeader.js
│   │   │   │   └── WorkspaceHeader.js
│   │   │   ├── modals/
│   │   │   │   ├── AddEnvVariableModal.js
│   │   │   │   ├── ConfirmActionModal.js
│   │   │   │   ├── EditEnvVariableModal.js
│   │   │   │   ├── GenAIUsageDisclaimer.js
│   │   │   │   ├── GenerateFlowTestModal.js
│   │   │   │   ├── ImportCollectionModal.js
│   │   │   │   ├── OpenCollectionModal.js
│   │   │   │   ├── OutputNodeExpandedModal.js
│   │   │   │   ├── SaveFlowModal.js
│   │   │   │   ├── SettingsModal.js
│   │   │   │   ├── create/
│   │   │   │   │   └── NewCollectionModal.js
│   │   │   │   ├── flow/
│   │   │   │   │   ├── AddVariableModal.js
│   │   │   │   │   └── NewFlowTestModal.js
│   │   │   │   └── sidebar/
│   │   │   │       ├── NewEnvironmentFileModal.js
│   │   │   │       └── NewLabelModal.js
│   │   │   ├── sideSheets/
│   │   │   │   └── FlowLogs.js
│   │   │   ├── sidebar/
│   │   │   │   ├── Empty.js
│   │   │   │   └── content/
│   │   │   │       ├── Collection.js
│   │   │   │       ├── Collections.js
│   │   │   │       ├── Environment.js
│   │   │   │       ├── Environments.js
│   │   │   │       └── index.js
│   │   │   └── workspace/
│   │   │       ├── EmptyWorkSpaceContent.js
│   │   │       └── WorkspaceContent.js
│   │   ├── organisms/
│   │   │   ├── AppNavBar.js
│   │   │   ├── SideBar.js
│   │   │   └── workspace/
│   │   │       └── Workspace.js
│   │   └── pages/
│   │       └── Home.js
│   ├── constants/
│   │   ├── AppNavBar.js
│   │   ├── Common.js
│   │   ├── ImportCollectionTypes.js
│   │   ├── ModalNames.js
│   │   ├── WorkspaceDirectory.js
│   │   └── sidebar/
│   │       └── Environnments.js
│   ├── index.css
│   ├── index.js
│   ├── ipc/
│   │   ├── collection.js
│   │   └── settings.js
│   ├── reportWebVitals.js
│   ├── routes/
│   │   ├── Main.js
│   │   └── index.js
│   ├── service/
│   │   ├── collection.js
│   │   └── settings.js
│   ├── setupTests.js
│   ├── stores/
│   │   ├── AppNavBarStore.js
│   │   ├── CanvasStore.js
│   │   ├── CollectionStore.js
│   │   ├── CommonStore.js
│   │   ├── EnvStore.js
│   │   ├── EventListenerStore.js
│   │   ├── SettingsStore.js
│   │   ├── TabStore.js
│   │   ├── collectionstore.test.js
│   │   ├── eventstore.test.js
│   │   ├── tabstore.test.js
│   │   └── utils.js
│   └── utils/
│       ├── common.js
│       ├── useRenderCount.js
│       └── useTelemetry.js
└── tailwind.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .changeset/README.md
================================================
# Changesets

Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)

We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)


================================================
FILE: .changeset/config.json
================================================
{
  "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
  "changelog": ["@changesets/cli/changelog", { "repo": "FlowTestAI/FlowTest" }],
  "commit": false,
  "fixed": [],
  "linked": [],
  "access": "public",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": [],
  "changedFilePatterns": ["src/**/"]
}


================================================
FILE: .changeset/early-colts-approve.md
================================================
---
'flowtestai-app': minor
---

Add support for google geminin function calling in generating flow


================================================
FILE: .changeset/honest-bags-fail.md
================================================
---
'flowtestai-app': minor
---

add support for bearer token auth type


================================================
FILE: .changeset/proud-ants-flash.md
================================================
---
'flowtestai-app': minor
---

allow request headers to be input by users


================================================
FILE: .eslintignore
================================================
# dependencies
**/node_modules

# production
/build

# intermal dependencies
/server


================================================
FILE: .eslintrc.js
================================================
module.exports = {
  env: {
    browser: true,
    es2021: true,
    jest: true,
  },
  extends: ['eslint:recommended', 'plugin:react/recommended', 'prettier'],
  overrides: [
    {
      env: {
        node: true,
      },
      files: ['.eslintrc.{js,cjs}'],
      parserOptions: {
        sourceType: 'script',
      },
    },
  ],
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module',
  },
  plugins: ['react', 'react-hooks', 'prettier'],
  rules: {
    'react/prop-types': 'off', // keeping off for first phase
    'no-unused-vars': 'off', // Getting a lot of Error for: '_' is assigned a value but never used  no-unused-vars. For now disabling this because  need to understand more about the use '_'.
  },
  settings: {
    'import/resolver': {
      node: {
        moduleDirectory: ['node_modules', './src/*'],
        extensions: ['.js', '.jsx', '.ts', '.tsx'],
      },
    },
  },
};


================================================
FILE: .github/CODEOWNERS
================================================
# Lines starting with '#' are comments.
# Each line is a file pattern followed by one or more owners.

# More details are here: https://help.github.com/articles/about-codeowners/

# The '*' pattern is global owners.

# Order is important. The last matching pattern has the most precedence.
# The folders are ordered as follows:

# In each subsection folders are ordered first by depth, then alphabetically.
# This should make it easy to add new rules without breaking existing ones.

# Global rule:
*           @jsajal

================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
**/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*

## build
**/dist
**/build

# db
.flowtest

# temp upload directory
uploads/

# env
.env
.env.example

# Ide
.vscode/

================================================
FILE: .npmrc
================================================
node-linker=hoisted #pnpm requirement when working with electron
ignore-workspace-root-check=true

================================================
FILE: .prettierrc
================================================
{
  "semi": true,
  "tabWidth": 2,
  "printWidth": 120,
  "singleQuote": true,
  "trailingComma": "all",
  "jsxSingleQuote": true,
  "bracketSpacing": true,
  "plugins": ["prettier-plugin-tailwindcss"]
}


================================================
FILE: CONTRIBUTING.md
================================================
# Contribution Guidelines

When contributing to `FlowTestAI`, whether on GitHub or in other community spaces:

- Be respectful, civil, and open-minded.
- Before opening a new pull request, try searching through the [issue tracker](https://github.com/FlowTestAI/FlowTest/issues) for known issues or fixes.
- If you want to make code changes based on your personal opinion(s), make sure you open an issue first describing the changes you want to make, and open a pull request only when your suggestions get approved by maintainers.

## How to Contribute

### Prerequisites

In order to not waste your time implementing a change that has already been declined, or is generally not needed, start by [opening an issue](https://github.com/FlowTestAI/FlowTest/issues/new/choose) describing the problem you would like to solve.

### Setup your environment locally

_Some commands will assume you have the GitHub CLI installed, if you haven't, consider [installing it](https://github.com/cli/cli#installation), but you can always use the Web UI if you prefer that instead._

In order to contribute to this project, you will need to fork the repository:

```bash
gh repo fork FlowTestAI/FlowTest
```

then, clone it to your local machine:

```bash
gh repo clone <your-github-name>/FlowTest
```

This project uses [pnpm](https://pnpm.io) as its package manager. Install it if you haven't already:

```bash
npm install -g pnpm@9.0.6
```

Then, install the project's dependencies:

```bash
pnpm install
```

### Implement your changes

This project is a monorepo. FlowTestAI is offered as a local electron desktop app. In lieu of that it has two major components, the main logical part of the application resides in `packages/flowtest-electron` and the renderer (UI) part of the application resides in `src`. We are also actively developing the CLI and it resides in `packages/flowtest-cli` directory.

Here are some useful scripts for when you are developing:

| Command         | Description                                        |
| --------------- | -------------------------------------------------- |
| `pnpm start`    | Builds and starts the FlowTest App on your desktop |
| `pnpm build`    | Builds the application for development use         |
| `pnpm format`   | Formats the code                                   |
| `pnpm lint`     | Lints the code                                     |
| `pnpm lint:fix` | Lints the code and fixes any errors                |
| `pnpm clean`    | Deletes all node_modules in the project            |

### When you're done

Please make a manual, functional test of your changes.

Check for formatting and linting errors:

```bash
pnpm lint && pnpmt format
```

When making commits, make sure to follow the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) guidelines, i.e. prepending the message with `feat:`, `fix:`, `chore:`, `docs:`, etc... You can use `git status` to double check which files have not yet been staged for commit:

```bash
git add <file> && git commit -m "feat/fix/chore/docs: commit message"
```

If your change should appear in the changelog, i.e. it changes some behavior of either the CLI or the outputted elctron application, it must be captured by `changeset`. If this does not apply to your contribution skip to the pr creation step.

Run the changeset:

```bash
pnpm changeset
```

and filling out the form with the appropriate information. Then, add the generated changeset to git:

```bash
git add .changeset/*.md && git commit -m "chore: add changeset"
```

When all that's done, it's time to file a pull request to upstream:

```bash
gh pr create --web
```

and fill out the title and body appropriately. Again, make sure to follow the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) guidelines for your title.

## Credits

This documented was inspired by the contributing guidelines for [t3-oss/create-t3-app](https://github.com/t3-oss/create-t3-app/blob/main/CONTRIBUTING.md).


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2023 Sajal Jain

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# FlowTestAI: Streamlining End-to-End API Testing

[![Release Notes](https://img.shields.io/github/release/FlowTestAI/FlowTest)](https://github.com/FlowTestAI/FlowTest/releases)
[![Linkedin](https://img.shields.io/badge/LinkedIn-blue?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/flowtestai)
[![Twitter Follow](https://img.shields.io/twitter/follow/FlowTestAI?style=social)](https://twitter.com/FlowTestAI)
[![Chat on Discord](https://img.shields.io/badge/chat-Discord-7289DA?logo=discord)](https://discord.gg/Pf9tdSjPeF)

💡 We are proud to announce that we were recently featured in a [LangChain](https://blog.langchain.dev/empowering-development-with-flowtestai/) blog post.

FlowTestAI is a powerful, code-agnostic tool designed to simplify the creation and execution of end-to-end API tests. With its intuitive interface and robust features, FlowTestAI empowers developers and QA teams to streamline their API testing process, improve collaboration, and gain valuable insights into their API performance.

<img width="1728" alt="Screenshot 2024-04-18 at 5 41 43 PM" src="https://github.com/FlowTestAI/FlowTest/assets/5829490/c04f6e3e-fe69-4d25-a008-ba558c8fe149">

## 🚀 Key Features

- **Low Code/No Code Solution**: Create complex end-to-end API tests without writing code.
- **Natural Language Processing**: Describe your test scenarios in plain English.
- **Support Leading LLMs**: Choose from a wide range of leading LLMs: OpenAI, AWS Bedrock, Google Gemini etc.
- **Drag-and-Drop Interface**: Visually design your API tests with ease.
- **OpenAPI Spec Integration**: Automatically parse and pre-fill request nodes from your OpenAPI specifications.
- **Cross-Platform Compatibility**: Available as an Desktop application for Mac, Windows, and Linux.
- **Local File System Integration**: Direct interaction with local file system for enhanced privacy and control.
- **Version Control Ready**: Easily collaborate using Git or any other VCS.
- **CI/CD Ready**: Run tests in CI pipelines with our CLI tool.
- **Advanced Analytics**: Gain insights into API performance and test results.

## 🛠️ Getting Started

### Desktop App Installation

1. Download FlowTestAI for your OS from our [releases page](https://github.com/FlowTestAI/FlowTest/releases).
2. Install and launch FlowTestAI like any other desktop application.
3. Start creating end-to-end API tests using natural language or drag-and-drop.
4. Save your work locally and use Git for version control, just like with traditional IDEs.

### CLI Installation (for CI/CD)

```bash
npm install -g flowtestai
```

https://www.npmjs.com/package/flowtestai

The CLI allows you to run flows created using FlowTestAI from command line interface making it easier to automate and run them in a CI/CD (continuous integration/development) fashion.

[README](https://github.com/FlowTestAI/FlowTest/blob/main/packages/flowtest-cli/README.md)

### Analytics Setup (Optional)

1. Visit https://www.useflowtest.ai/
2. Go to Products -> Analytics -> Get Access Key Pairs
3. For CLI: Export key pairs as environment variables
4. For IDE: Open Settings and paste the access key pairs
5. Now start publishing scans for each test run.

## 📚 Documentation

https://flowtestai.gitbook.io/flowtestai

## Setup

## 💻 Production

FlowTestAI is an electron app that runs entirely in your local environment interacting with your local file system just like other IDE(s) out there like VSCode, Intellij etc. The platform-specific binaries are available for download from our GitHub releases. We currently offer [binaries for macOS](https://github.com/FlowTestAI/FlowTest/releases), with versions for Windows and Linux under development 🚧. If you require a binary for a specific platform, please let us know in the Discussions section. We will prioritize your request accordingly.

## 🔧 Development

### Prerequisite

This package uses version >= 18 of Node.js. There are different ways that you can install Node.js, following are steps for [Node Verson Manager or NVM](https://github.com/nvm-sh/nvm). If you need steps for other methods than NVM then please check [Official Node.js documentation](https://nodejs.org/en/download/package-manager). Here is a sample walkthrough installing version 18.

1. Installs nvm (Node Version Manager)

   ```bash
   curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
   ```

2. Download and install Node.js

   ```bash
   nvm install 18
   ```

3. Verifies the right Node.js version is in the environment

   ```bash
   node -v # should print `v18.20.2`
   ```

4. Verifies the right NPM version is in the environment

   ```bash
   npm -v # should print `10.5.0`
   ```

### Main setup

1. Clone the repository

   ```bash
   git clone https://github.com/FlowTestAI/FlowTest.git
   ```

2. Go into repository folder

   ```bash
   cd FlowTest
   ```

3. This project uses pnpm. Use [corepack](https://github.com/nodejs/corepack) to enable the required pnpm version:

   ```bash
   corepack enable pnpm
   ```

   or install with npm

   ```bash
   npm install -g pnpm@9.0.6
   ```

4. Install all project dependencies:

   ```bash
   pnpm install
   ```

5. Build and start the app:

   ```bash
   pnpm start
   ```

   The app should start as a normal desktop app

   NOTE: if you use npm and corepack to install pnpm you will have two instances of pnpm. Make sure the version you're using is the correct version for the repo. Check the [pnpm docs](https://pnpm.io/installation) and [corepack](https://github.com/nodejs/corepack) for troubleshooting. Pnpm installed with npm will overrun corepacks pnpm instance.

## 🤝 Contribution

_"Little drops of water make a mighty ocean"_

No contribution is small even if it means fixing a spelling mistake. Follow our contributing guide below.
https://github.com/FlowTestAI/FlowTest/blob/main/CONTRIBUTING.md

Fun fact: our contributing guide itself was an external contribution 🍺

## 🌟 Support

- ❓ QNA: feel free to ask questions, request new features or start a constructive discussion here [discussion](https://github.com/FlowTestAI/FlowTest/discussions)
- 🐛 Issues: Feel free to raise issues here [issues](https://github.com/FlowTestAI/FlowTest/issues) (contributing guidelines coming soon..)
- 🔄 Integration: If you want to explore how you can use this tool in your day to day activities or integrate with your existing stack or in general want to chat, you can reach out to us at any of our [social media handles](https://flowtestai.gitbook.io/flowtestai) or email me at jsajal1993@gmail.com.
- 🔐 Our tool integrates with various leading Large Lanugage Models (LLMs) if you wish to use the natural language to flow translation feature. You can request their api keys:
  - [OpenAI](https://platform.openai.com/)
  - [AWS Bedrock](https://console.aws.amazon.com/bedrock/)
  - [Google GEMINI](https://ai.google.dev/gemini-api/docs/api-key)
  - [Local AI] (Coming Soon...)

## 📜 License

Source code in this repository is made available under the [MIT License](LICENSE).

## Connect with Us

- Website: [useflowtest.ai](https://www.useflowtest.ai/)
- Email: jsajal1993@gmail.com


================================================
FILE: jsconfig.json
================================================
{
  "compilerOptions": {
    "baseUrl": "src",
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ]
  },
  "include": [
    "src"
  ]
}

================================================
FILE: package.json
================================================
{
  "name": "flowtest",
  "version": "1.0.0",
  "private": true,
  "homepage": ".",
  "packageManager": "pnpm@9.0.6",
  "engines": {
    "node": ">=18.17.0"
  },
  "scripts": {
    "start": "pnpm run build && cd packages/flowtest-electron && pnpm start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "lint": "eslint . --ext .js,.json,.cjs",
    "lint:fix": "eslint --fix . --ext .js,.json,.cjs",
    "format": "prettier --write '**/*.{js,jsx,ts,tsx,css,md,json}' --config ./.prettierrc",
    "clean": "rm -rf node_modules/ && rm -rf packages/flowtest-electron/node_modules/"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "dependencies": {
    "@codemirror/commands": "^6.5.0",
    "@codemirror/lang-json": "^6.0.1",
    "@codemirror/language": "^6.10.1",
    "@codemirror/state": "^6.4.1",
    "@codemirror/view": "^6.26.3",
    "@emotion/react": "^11.11.1",
    "@emotion/styled": "^11.11.0",
    "@headlessui/react": "^1.7.18",
    "@heroicons/react": "^2.1.1",
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "@tippyjs/react": "^4.2.6",
    "allotment": "^1.20.0",
    "autoprefixer": "^10.4.18",
    "axios": "^1.5.1",
    "codemirror": "^6.0.1",
    "date-fns": "^3.6.0",
    "eslint-import-resolver-alias": "^1.1.2",
    "eslint-plugin-import": "^2.29.1",
    "form-data": "^4.0.0",
    "immer": "^10.0.4",
    "lodash": "^4.17.21",
    "mousetrap": "^1.6.5",
    "notistack": "^3.0.1",
    "postcss": "^8.4.35",
    "posthog-node": "^4.0.1",
    "react": "^18.2.0",
    "react-copy-to-clipboard": "^5.1.0",
    "react-custom-scrollbars": "^4.2.1",
    "react-dom": "^18.2.0",
    "react-edit-text": "^5.1.1",
    "react-icons": "^5.0.1",
    "react-json-view-lite": "^1.4.0",
    "react-perfect-scrollbar": "^1.5.8",
    "react-router": "^6.15.0",
    "react-router-dom": "^6.22.2",
    "react-scripts": "5.0.1",
    "react-sliding-pane": "^7.3.0",
    "react-toastify": "^10.0.5",
    "react-tooltip": "^5.26.2",
    "reactflow": "^11.8.3",
    "socket.io-client": "^4.7.4",
    "tailwindcss": "^3.4.1",
    "typescript": "4",
    "web-vitals": "^2.1.4",
    "zustand": "^4.5.2"
  },
  "devDependencies": {
    "@changesets/cli": "^2.27.1",
    "@tailwindcss/typography": "^0.5.10",
    "@types/node": "20.11.5",
    "daisyui": "^4.7.2",
    "eslint": "^8.56.0",
    "eslint-config-prettier": "^9.1.0",
    "eslint-plugin-import-alias": "^1.2.0",
    "eslint-plugin-prettier": "^5.1.3",
    "eslint-plugin-react": "^7.33.2",
    "eslint-plugin-react-hooks": "^4.6.0",
    "prettier": "^3.2.5",
    "prettier-plugin-tailwindcss": "^0.5.11"
  }
}


================================================
FILE: packages/flowtest-cli/LICENSE.md
================================================
MIT License

Copyright (c) 2023 Sajal Jain

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: packages/flowtest-cli/README.md
================================================
# flowtestai-cli

With FlowTestAI CLI, you can now run your end to end flows, constructed using FlowTestAI, directly from command line.

This makes it easier to run your tests in different environments, automate your testing process, and integrate your tests with your continuous integration and deployment workflows.

## Installation

To install the FlowTestAI CLI, use the node package manager of your choice, such as NPM:

```bash
npm install -g flowtestai
```

## Getting started

Navigate to the root directory of your collection, and then run:

```bash
flow run help
```

This command will give you various options you can use to run a flow. You can also run a single flow by specifying its filename with the `--file` or `-f` option:

```bash
flow run -f test.flow
```

Or run a requests inside a subfolder:

```bash
flow run -f folder/subfolder/test.flow
```

If you need to use an environment, you can specify it with the `--env` or `-e` option:

```bash
flow run -f test.flow -e environments/test.env
```

If you need to publish the results of your flow runs for further analysis, you can specify the `-s` option. Request your access key pairs from https://www.useflowtest.ai/ and then run export $FLOWTEST_ACCESS_ID and $FLOWTEST_ACCESS_KEY before publishing:

```bash
flow run -f test.flow -e environments/test.env -s
```

## Demo

![demo1](assets/demo1.png)
![demo2](assets/demo2.png)

## Support

If you encounter any issues or have any feedback or suggestions, please raise them on our [GitHub repository](https://github.com/FlowTestAI/FlowTest)

Thank you for using FlowTestAI CLI!

## Changelog

See [https://github.com/FlowTestAI/FlowTest/releases](https://github.com/FlowTestAI/FlowTest/releases)

## License

[MIT](LICENSE.md)


================================================
FILE: packages/flowtest-cli/bin/axiosClient.js
================================================
// lib/axiosClient.ts
const axios = require('axios');
const axiosRetry = require('axios-retry').default;

const baseUrl = 'https://www.useflowtest.ai';

const axiosClient = axios.create({
  baseURL: `${baseUrl}/api`,
  headers: {
    'Content-Type': 'application/json',
  },
});

axiosRetry(axiosClient, {
  retries: 3, // Number of retries
  retryDelay: (retryCount) => {
    return retryCount * 1000; // Time interval between retries (1000 ms = 1 second)
  },
  retryCondition: (error) => {
    // Retry on network errors or rate limit errors or 5xx server errors
    return error.response?.status === 500 || error.response?.status === 429 || error.code === 'ECONNABORTED';
  },
});

module.exports = {
  baseUrl,
  axiosClient,
};


================================================
FILE: packages/flowtest-cli/bin/index.js
================================================
#!/usr/bin/env node

const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');
const chalk = require('chalk');
const readFile = require('../utils/readfile');
const { serialize } = require('../utils/flowparser/parser');
const { Graph } = require('../graph/Graph');
const { cloneDeep } = require('lodash');
const dotenv = require('dotenv');
const { GraphLogger, LogLevel } = require('../graph/GraphLogger');
const { baseUrl, axiosClient } = require('./axiosClient');
require('dotenv').config();

const getEnvVariables = (pathname) => {
  const content = readFile(pathname);
  const buf = Buffer.from(content);
  const parsed = dotenv.parse(buf);
  return parsed;
};

function bytesToBase64(bytes) {
  const binString = Array.from(bytes, (byte) => String.fromCodePoint(byte)).join('');
  return btoa(binString);
}

// Define the CLI application using yargs
const argv = yargs(hideBin(process.argv))
  .usage('Usage: $0 <command> [options]')
  .command(
    'run',
    'Run a flow',
    (yargs) => {
      return yargs
        .option('file', {
          alias: 'f',
          describe: 'path of the flow to run',
          demandOption: true,
          type: 'string',
        })
        .option('env', {
          alias: 'e',
          describe: 'path of the environment file',
          demandOption: false,
          type: 'string',
        })
        .option('timeout', {
          alias: 't',
          describe: 'timeout for flow run in ms',
          demandOption: false,
          type: 'number',
        })
        .option('scan', {
          alias: 's',
          describe: 'generate and upload flow scan',
        });
    },
    async (argv) => {
      if (argv.file.toLowerCase().endsWith(`.flow`)) {
        let content = undefined;
        try {
          content = readFile(argv.file);
        } catch (error) {
          console.error(chalk.red(`${error}`));
          process.exit(1);
        }
        try {
          const flowData = serialize(JSON.parse(content));
          // output json output to a file

          const logger = new GraphLogger();
          const startTime = Date.now();
          const g = new Graph(
            cloneDeep(flowData.nodes),
            cloneDeep(flowData.edges),
            startTime,
            argv.timeout ? argv.timeout : 60000,
            argv.env ? getEnvVariables(argv.env) : {},
            logger,
          );
          console.log(chalk.blue('Running Flow \n'));
          console.log(
            chalk.yellow(
              'Right now CLI commands must be run from root directory of collection. We will gradually add support to run commands from anywhere inside the collection. \n',
            ),
          );
          const result = await g.run();
          console.log('\n');
          if (result.status === 'Success') {
            console.log(chalk.bold('Flow Run: ') + chalk.green(`   ✓ `) + chalk.dim(result.status));
          } else {
            console.log(chalk.bold('Flow Run: ') + chalk.red(`   ✕ `) + chalk.dim(result.status));
          }
          const time = Date.now() - startTime;
          logger.add(LogLevel.INFO, `Total time: ${time} ms`);
          console.log(chalk.bold('Total Time: ') + chalk.dim(`${time} ms`));
          //console.log(logger.get());

          if (argv.scan) {
            const data = {
              scan_metadata: {
                version: 1,
                name: argv.file.toString(),
                status: result.status,
                time,
              },
              scan: bytesToBase64(new TextEncoder().encode(JSON.stringify(logger.get()))),
            };
            const accessId = process.env.FLOWTEST_ACCESS_ID;
            const accessKey = process.env.FLOWTEST_ACCESS_KEY;
            if (!accessId || accessId.trim() === '' || !accessKey || accessKey.trim() === '') {
              console.log(chalk.red(`   ✕ `) + chalk.dim('Unable to upload flow scan'));
              console.log(
                chalk.yellow(`Failed to detect access key pairs. Make sure to set environment variables properly.`),
              );
              console.log(chalk.yellow(`   export FLOWTEST_ACCESS_ID="<<FLOWTEST_ACCESS_ID>>"`));
              console.log(chalk.yellow(`   export FLOWTEST_ACCESS_KEY="<<FLOWTEST_ACCESS_KEY>>"`));
            } else {
              try {
                const response = await axiosClient.post('/upload', data, {
                  headers: {
                    'Content-Type': 'application/json',
                    'x-access-id': accessId,
                    'x-access-key': accessKey,
                  },
                });
                console.log(chalk.bold('Flow Scan: ') + chalk.dim(`${baseUrl}/scan/${response.data.data[0].id}`));
              } catch (error) {
                if (error?.response) {
                  if (error.response?.status >= 400 && error.response?.status < 500) {
                    console.log(chalk.red(`   ${JSON.stringify(error.response?.data)}`));
                  }

                  if (error.response?.status === 500) {
                    console.log(chalk.red('   Internal Server Error'));
                  }
                }
                console.log(chalk.red(`   ✕ `) + chalk.dim('Unable to upload flow scan'));
              }
            }
          } else {
            console.log('\n');
            console.log(
              chalk.yellow(
                'Enable flow scans today to get more value our of your APIs. Get your access key pairs at https://www.useflowtest.ai/ \n',
              ),
            );
          }

          process.exit(1);
          //console.log(chalk.green(JSON.stringify(result)));
        } catch (error) {
          console.error(chalk.red(`Internal error running flow`));
          process.exit(1);
        }
      } else {
        console.error(chalk.red('Input file is not a flow file'));
        process.exit(1);
      }
    },
  )
  .help()
  .alias('help', 'h')
  .parse();


================================================
FILE: packages/flowtest-cli/graph/Graph.js
================================================
// assumption is that apis are giving json as output

const { cloneDeep } = require('lodash');
const authNode = require('./compute/authnode');
const assertNode = require('./compute/assertnode');
const requestNode = require('./compute/requestNode');
const setVarNode = require('./compute/setvarnode');
const chalk = require('chalk');
const path = require('path');
const Node = require('./compute/node');
const { LogLevel } = require('./GraphLogger');
const readFile = require('../utils/readfile');
const { serialize } = require('../utils/flowparser/parser');

class nestedFlowNode extends Node {
  constructor(nodes, edges, startTime, timeout, initialEnvVars, logger) {
    super('flowNode');
    try {
      this.internalGraph = new Graph(nodes, edges, startTime, timeout, initialEnvVars, logger);
    } catch (error) {
      console.log(error);
    }
  }

  async evaluate() {
    return this.internalGraph.run();
  }
}

class Graph {
  constructor(nodes, edges, startTime, timeout, initialEnvVars, logger) {
    this.nodes = nodes;
    this.edges = edges;
    this.timeout = timeout;
    this.startTime = startTime;
    this.graphRunNodeOutput = {};
    this.auth = undefined;
    this.envVariables = initialEnvVars;
    this.logger = logger;
  }

  #checkTimeout() {
    return Date.now() - this.startTime > this.timeout;
  }

  #computeConnectingEdge(node, result) {
    let connectingEdge = undefined;

    if (node.type === 'assertNode') {
      if (result.output === true) {
        connectingEdge = this.edges.find((edge) => edge.sourceHandle == 'true' && edge.source === node.id);
      } else {
        connectingEdge = this.edges.find((edge) => edge.sourceHandle == 'false' && edge.source === node.id);
      }
    } else {
      if (result.status === 'Success') {
        connectingEdge = this.edges.find((edge) => edge.source === node.id);
      }
    }

    return connectingEdge;
  }

  #computeDataFromPreviousNodes(node) {
    var prevNodesData = {};
    // a request node is allowed multiple incoming edges
    this.edges.forEach((edge) => {
      if (edge.target === node.id) {
        if (this.graphRunNodeOutput[edge.source]) {
          prevNodesData = {
            ...prevNodesData,
            ...this.graphRunNodeOutput[edge.source],
          };
        }
      }
    });
    return prevNodesData;
  }

  async #computeNode(node) {
    let result = undefined;
    const prevNodeOutputData = this.#computeDataFromPreviousNodes(node);

    try {
      if (node.type === 'outputNode') {
        console.log('Output Node');
        console.log(chalk.green(`   ✓ `) + chalk.dim(`${JSON.stringify(prevNodeOutputData)}`));
        this.logger.add(LogLevel.INFO, '', { type: 'outputNode', data: { output: prevNodeOutputData } });
        result = {
          status: 'Success',
          data: prevNodeOutputData,
        };
      }

      if (node.type === 'assertNode') {
        const eNode = new assertNode(
          node.data.operator,
          node.data.variables,
          prevNodeOutputData,
          this.envVariables,
          this.logger,
        );
        if (eNode.evaluate()) {
          console.log(chalk.green(`   ✓ `) + chalk.dim('True'));
          result = {
            status: 'Success',
            data: prevNodeOutputData,
            output: true,
          };
        } else {
          console.log(chalk.red(`   ✕ `) + chalk.dim('False'));
          result = {
            status: 'Success',
            data: prevNodeOutputData,
            output: false,
          };
        }
      }

      if (node.type === 'delayNode') {
        const delay = node.data.delay;
        const wait = (ms) => {
          return new Promise((resolve) => setTimeout(resolve, Math.min(ms, this.timeout)));
        };
        console.log('Delay Node: ' + chalk.green(`....waiting for: ${delay} ms`));
        await wait(delay);
        this.logger.add(LogLevel.INFO, '', { type: 'delayNode', data: { delay } });
        result = {
          status: 'Success',
          data: prevNodeOutputData,
        };
      }

      if (node.type === 'authNode') {
        console.log('Authentication Node');
        const aNode = new authNode(node.data, this.envVariables, this.logger);
        this.auth = node.data.type ? aNode.evaluate() : undefined;
        result = {
          status: 'Success',
          data: prevNodeOutputData,
        };
      }

      if (node.type === 'requestNode') {
        console.log('Request Node');
        const rNode = new requestNode(node.data, prevNodeOutputData, this.envVariables, this.auth, this.logger);
        result = await rNode.evaluate();
        // add post response variables if any
        if (result.postRespVars) {
          this.envVariables = {
            ...this.envVariables,
            ...result.postRespVars,
          };
        }
      }

      if (node.type === 'flowNode') {
        console.log('Flow Node (Nested graph)');
        const content = readFile(path.join(process.cwd(), node.data.relativePath));
        const flowData = serialize(JSON.parse(content));
        if (flowData) {
          const cNode = new nestedFlowNode(
            cloneDeep(flowData.nodes),
            cloneDeep(flowData.edges),
            this.startTime,
            this.timeout,
            this.envVariables,
            this.logger,
          );
          result = await cNode.evaluate();
          this.envVariables = result.envVars;
        } else {
          result = {
            status: 'Success',
            data: prevNodeOutputData,
          };
        }
      }

      if (node.type === 'setVarNode') {
        console.log('Set Variable Node');
        const sNode = new setVarNode(node.data, prevNodeOutputData, this.envVariables);
        const newVariable = sNode.evaluate();
        if (newVariable != undefined) {
          console.log(chalk.green(`   ✓ `) + chalk.dim(`Set variable: ${JSON.stringify(newVariable)}`));
          this.logger.add(LogLevel.INFO, '', {
            type: 'setVarNode',
            data: {
              name: Object.keys(newVariable)[0],
              value: newVariable[Object.keys(newVariable)[0]],
            },
          });
          this.envVariables = {
            ...this.envVariables,
            ...newVariable,
          };
        }
        result = {
          status: 'Success',
          data: prevNodeOutputData,
        };
      }

      if (this.#checkTimeout()) {
        throw Error(`Timeout of ${this.timeout} ms exceeded, stopping graph run`);
      }
    } catch (err) {
      console.log(chalk.red(`Flow failed at: ${JSON.stringify(node.data)} due to ${err}`));
      this.logger.add(LogLevel.ERROR, `Flow failed due to ${err}`, {
        type: 'errorNode',
        data: node.data,
      });
      return {
        status: 'Failed',
      };
    }

    if (result === undefined) {
      console.log(chalk.red(`Flow failed due to failure to evaluate result at node: ${node.data}`));
      this.logger.add(LogLevel.ERROR, 'Flow failed due to failure to evaluate result', {
        type: 'errorNode',
        data: node.data,
      });
      return {
        status: 'Failed',
      };
    } else {
      const connectingEdge = this.#computeConnectingEdge(node, result);

      if (connectingEdge != undefined) {
        const nextNode = this.nodes.find(
          (node) =>
            ['requestNode', 'outputNode', 'assertNode', 'delayNode', 'authNode', 'flowNode', 'setVarNode'].includes(
              node.type,
            ) && node.id === connectingEdge.target,
        );
        this.graphRunNodeOutput[node.id] = result.data ? result.data : {};
        return this.#computeNode(nextNode);
      } else {
        return result;
      }
    }
  }

  async run() {
    this.graphRunNodeOutput = {};

    console.log(chalk.green('Start Flowtest'));
    this.logger.add(LogLevel.INFO, 'Start Flowtest');

    const startNode = this.nodes.find((node) => node.type === 'startNode');
    if (startNode == undefined) {
      console.log(chalk.red(`✕ `) + chalk.red('No start node found'));
      console.log(chalk.red('End Flowtest'));
      this.logger.add(LogLevel.INFO, 'No start node found');
      this.logger.add(LogLevel.INFO, 'End Flowtest');
      return {
        status: 'Success',
        envVars: this.envVariables,
      };
    }
    const connectingEdge = this.edges.find((edge) => edge.source === startNode.id);

    // only start computing graph if initial node has the connecting edge
    if (connectingEdge != undefined) {
      const firstNode = this.nodes.find((node) => node.id === connectingEdge.target);
      const result = await this.#computeNode(firstNode);
      if (result.status == 'Failed') {
        console.log(chalk.red('End Flowtest'));
      } else {
        console.log(chalk.green('End Flowtest'));
      }
      this.logger.add(LogLevel.INFO, 'End Flowtest');
      return {
        status: result.status,
        envVars: this.envVariables,
      };
    } else {
      console.log(chalk.green('End Flowtest'));
      this.logger.add(LogLevel.INFO, 'End Flowtest');
      return {
        status: 'Success',
        envVars: this.envVariables,
      };
    }
  }
}

module.exports = { Graph };


================================================
FILE: packages/flowtest-cli/graph/GraphLogger.js
================================================
const LogLevel = Object.freeze({
  INFO: 'info',
  WARN: 'warn',
  ERROR: 'error',
});

class GraphLogger {
  constructor() {
    this.logs = [];
  }

  add(logLevel, message, node) {
    this.logs.push({
      level: logLevel,
      timestamp: new Date().toISOString(),
      message,
      node,
    });
  }

  get() {
    return this.logs;
  }
}

module.exports = {
  GraphLogger,
  LogLevel,
};


================================================
FILE: packages/flowtest-cli/graph/compute/assertnode.js
================================================
const AssertOperators = require('../constants/assertOperators');
const { computeNodeVariable } = require('./utils');
const Node = require('./node');
const chalk = require('chalk');
const { LogLevel } = require('../GraphLogger');

class assertNode extends Node {
  constructor(operator, variables, prevNodeOutputData, envVariables, logger) {
    super('assertNode');
    this.operator = operator;
    this.variables = variables;
    this.prevNodeOutputData = prevNodeOutputData;
    this.logger = logger;
    this.envVariables = envVariables;
  }

  getVariableValue(variable) {
    if (variable.type.toLowerCase() === 'variable') {
      if (Object.prototype.hasOwnProperty.call(this.envVariables, variable.value)) {
        return this.envVariables[variable.value];
      } else {
        throw Error(`Cannot find value of variable ${variable.value}`);
      }
    } else {
      return computeNodeVariable(variable, this.prevNodeOutputData);
    }
  }

  evaluate() {
    //console.log('Evaluating an assert node');
    const var1 = this.getVariableValue(this.variables.var1);
    const var2 = this.getVariableValue(this.variables.var2);

    const operator = this.operator;
    if (operator == undefined) {
      throw Error('Operator undefined');
    }
    // this.logs.push(
    //   `Assert var1: ${JSON.stringify(var1)} of type: ${typeof var1}, var2: ${JSON.stringify(var2)} of type: ${typeof var2} with operator: ${operator}`,
    // );
    console.log(
      'Assert Node: ' +
        chalk.green(
          `Assert var1: ${JSON.stringify(var1)} of type: ${typeof var1}, var2: ${JSON.stringify(var2)} of type: ${typeof var2} with operator: ${operator}`,
        ),
    );
    let result;
    switch (operator) {
      case AssertOperators.isEqualTo:
        result = var1 === var2;
        break;
      case AssertOperators.isNotEqualTo:
        result = var1 != var2;
        break;
      case AssertOperators.isGreaterThan:
        result = var1 > var2;
        break;
      case AssertOperators.isLessThan:
        result = var1 < var2;
        break;
      default:
        throw Error('Unsupported operator');
    }
    this.logger.add(LogLevel.INFO, '', { type: 'assertNode', data: { var1, var2, operator, result } });

    return result;
  }
}

module.exports = assertNode;


================================================
FILE: packages/flowtest-cli/graph/compute/authnode.js
================================================
const { computeVariables } = require('./utils');
const Node = require('./node');
const chalk = require('chalk');
const { LogLevel } = require('../GraphLogger');

class authNode extends Node {
  constructor(nodeData, envVariables, logger) {
    super('authNode');
    (this.nodeData = nodeData), (this.envVariables = envVariables);
    this.logger = logger;
  }

  evaluate() {
    //console.log('Evaluating an auth node');
    if (this.nodeData.type === 'basic-auth') {
      console.log(chalk.green(`   ✓ `) + chalk.dim('.....setting basic authentication'));
      this.logger.add(LogLevel.INFO, '', { type: 'authNode', data: { authType: 'Basic Authentication' } });
      const username = computeVariables(this.nodeData.username, this.envVariables);
      const password = computeVariables(this.nodeData.password, this.envVariables);

      return {
        type: 'basic-auth',
        username,
        password,
      };
    } else if (this.nodeData.type === 'bearer-token') {
      this.logger.add(LogLevel.INFO, '', { type: 'authNode', data: { authType: 'Bearer Token' } });
      const token = computeVariables(this.nodeData.token, this.envVariables);
      return {
        type: 'bearer-token',
        token,
      };
    } else if (this.nodeData.type === 'no-auth') {
      console.log(chalk.green(`   ✓ `) + chalk.dim('.....using no authentication'));
      this.logger.add(LogLevel.INFO, '', { type: 'authNode', data: { authType: 'No Authentication' } });
      return {
        type: 'no-auth',
      };
    } else {
      throw Error(`auth type: ${this.nodeData.type} is not valid`);
    }
  }
}

module.exports = authNode;


================================================
FILE: packages/flowtest-cli/graph/compute/node.js
================================================
class Node {
  constructor(type) {
    this.type = type;
  }

  evaluate() {
    throw new Error('Evaluate method must be implemented by subclasses');
  }
}

module.exports = Node;


================================================
FILE: packages/flowtest-cli/graph/compute/requestnode.js
================================================
const { computeNodeVariables, computeVariables } = require('./utils');
const Node = require('./node');
const axios = require('axios');
const chalk = require('chalk');
const { LogLevel } = require('../GraphLogger');
const FormData = require('form-data');
const { extend, cloneDeep } = require('lodash');
const fs = require('fs');
const path = require('path');

const newAbortSignal = () => {
  const abortController = new AbortController();
  setTimeout(() => abortController.abort(), 60000 || 0);

  return abortController.signal;
};

/** web platform: blob. */
const convertBase64ToBlob = async (base64) => {
  const response = await fetch(base64);
  const blob = await response.blob();
  return blob;
};

class requestNode extends Node {
  constructor(nodeData, prevNodeOutputData, envVariables, auth, logger) {
    super('requestNode');
    this.nodeData = nodeData;
    this.prevNodeOutputData = prevNodeOutputData;
    this.envVariables = envVariables;
    this.auth = auth;
    this.logger = logger;
  }

  async evaluate() {
    // step1 evaluate pre request variables of this node
    const evalVariables = computeNodeVariables(this.nodeData.preReqVars, this.prevNodeOutputData);

    const variablesDict = {
      ...this.envVariables,
      ...evalVariables,
    };

    // step2 replace variables in url with value
    const finalUrl = computeVariables(this.nodeData.url, variablesDict);

    // step 3
    const rawRequest = this.formulateRequest(finalUrl, variablesDict);

    console.log(chalk.green(`   ✓ `) + chalk.dim(`type = ${this.nodeData.requestType.toUpperCase()}`));
    console.log(chalk.green(`   ✓ `) + chalk.dim(`url = ${finalUrl}`));

    const { request, response } = await this.runHttpRequest(rawRequest);

    if (response.error) {
      console.log(chalk.red(`   ✕ `) + chalk.dim(`Request failed: ${JSON.stringify(response.error)}`));
      this.logger.add(LogLevel.ERROR, 'HTTP request failed', {
        type: 'requestNode',
        data: {
          request,
          response: response.error,
          preReqVars: evalVariables,
        },
      });
      return {
        status: 'Failed',
      };
    } else {
      console.log(chalk.green(`   ✓ `) + chalk.dim(`Request successful: ${JSON.stringify(response)}`));
      if (this.nodeData.postRespVars) {
        const evalPostRespVars = computeNodeVariables(this.nodeData.postRespVars, response.data);
        this.logger.add(LogLevel.INFO, 'HTTP request success', {
          type: 'requestNode',
          data: {
            request,
            response,
            preReqVars: evalVariables,
            postRespVars: evalPostRespVars,
          },
        });
        return {
          status: 'Success',
          data: response.data,
          postRespVars: evalPostRespVars,
        };
      }
      this.logger.add(LogLevel.INFO, 'HTTP request success', {
        type: 'requestNode',
        data: {
          request,
          response,
          preReqVars: evalVariables,
        },
      });
      return {
        status: 'Success',
        data: response.data,
      };
    }
  }

  formulateRequest(finalUrl, variablesDict) {
    let restMethod = this.nodeData.requestType.toLowerCase();
    let headers = {};
    let requestData = undefined;

    if (this.nodeData.requestBody) {
      if (this.nodeData.requestBody.type === 'raw-json') {
        headers['content-type'] = 'application/json';
        requestData = this.nodeData.requestBody.body
          ? JSON.parse(computeVariables(this.nodeData.requestBody.body, variablesDict))
          : JSON.parse('{}');
      } else if (this.nodeData.requestBody.type === 'form-data') {
        headers['content-type'] = 'multipart/form-data';
        const params = cloneDeep(this.nodeData.requestBody.body);
        requestData = params;
      }
    }

    if (this.nodeData.headers && this.nodeData.headers.length > 0) {
      this.nodeData.headers.map((pair, index) => {
        headers[computeVariables(pair.name, variablesDict)] = computeVariables(pair.value, variablesDict);
      });
    }

    if (this.auth && this.auth?.type === 'bearer-token') {
      headers['Authorization'] = `Bearer ${this.auth.token}`;
    }

    const options = {
      method: restMethod,
      url: finalUrl,
      headers,
      data: requestData,
    };

    if (this.auth && this.auth?.type === 'basic-auth') {
      options.auth = {};
      options.auth.username = this.auth.username;
      options.auth.password = this.auth.password;
    }

    return options;
  }

  async runHttpRequest(request) {
    let requestSent;
    try {
      if (request.headers['content-type'] === 'multipart/form-data') {
        const formData = new FormData();
        const params = request.data;
        await params.map(async (param, index) => {
          if (param.type === 'text') {
            formData.append(param.key, param.value);
          }

          if (param.type === 'file') {
            let trimmedFilePath = param.value.trim();

            formData.append(param.key, fs.createReadStream(trimmedFilePath), path.basename(trimmedFilePath));
          }
        });

        request.data = formData;
        extend(request.headers, formData.getHeaders());
      }

      requestSent = {
        url: request.url,
        method: request.method,
        headers: request.headers,
        // form data obj gets serialized here so that it can be sent over wire
        // otherwise ipc communication errors out
        data: request.data ? JSON.parse(JSON.stringify(request.data)) : request.data,
      };

      const result = await axios({
        ...request,
        signal: newAbortSignal(),
      });

      return {
        request: requestSent,
        response: {
          status: result.status,
          statusText: result.statusText,
          data: result.data,
          headers: result.headers,
        },
      };
    } catch (error) {
      if (error?.response) {
        return {
          request: requestSent,
          response: {
            error: {
              status: error.response.status,
              statusText: error.response.statusText,
              data: error.response.data,
              headers: error.response.headers,
            },
          },
        };
      } else {
        return {
          request: requestSent,
          response: {
            error: {
              status: '',
              statusText: '',
              data: `An error occurred while running the request : ${error?.message}`,
            },
          },
        };
      }
    }
  }
}

module.exports = requestNode;


================================================
FILE: packages/flowtest-cli/graph/compute/setvarnode.js
================================================
const { computeNodeVariable } = require('./utils');
const Node = require('./node');
const EvaluateOperators = require('../constants/evaluateOperators');

class setVarNode extends Node {
  constructor(nodeData, prevNodeOutputData, envVariables) {
    super('setVarNode');
    this.nodeData = nodeData;
    this.prevNodeOutputData = prevNodeOutputData;
    this.envVariables = envVariables;
  }

  getVariableValue(variable) {
    if (variable.type.toLowerCase() === 'variable') {
      if (Object.prototype.hasOwnProperty.call(this.envVariables, variable.value)) {
        return this.envVariables[variable.value];
      } else {
        throw Error(`Cannot find value of variable ${variable.value}`);
      }
    } else {
      return computeNodeVariable(variable, this.prevNodeOutputData);
    }
  }

  evaluate() {
    //console.log('Evaluating set variable node');
    if (this.nodeData.variable) {
      if (this.nodeData.variable.name && this.nodeData.variable.name.trim() != '') {
        const vName = this.nodeData.variable.name;
        const vType = this.nodeData.variable.type.trim();
        if (['String', 'Number', 'Boolean', 'Now', 'Select'].includes(vType)) {
          const value = computeNodeVariable(this.nodeData.variable, this.prevNodeOutputData);
          return {
            [vName]: value,
          };
        } else if (vType === 'Expression') {
          const variables = this.nodeData.variable.value.variables;
          if (variables && variables.var1 && variables.var2) {
            const var1 = this.getVariableValue(this.nodeData.variable.value.variables.var1);
            const var2 = this.getVariableValue(this.nodeData.variable.value.variables.var2);

            const operator = this.nodeData.variable.value.operator;
            if (operator == undefined) {
              throw 'Operator undefined';
            }
            if (operator == EvaluateOperators.Add) {
              if (typeof var1 !== 'number' || typeof var2 !== 'number') {
                throw Error(`Cannot perform ${typeof var1} + ${typeof var2}`);
              }
              return {
                [vName]: var1 + var2,
              };
            } else if (operator == EvaluateOperators.Subtract) {
              if (typeof var1 !== 'number' || typeof var2 !== 'number') {
                throw Error(`Cannot perform ${typeof var1} + ${typeof var2}`);
              }
              return {
                [vName]: var1 - var2,
              };
            }
          }
        }
      }
    }
  }
}

module.exports = setVarNode;


================================================
FILE: packages/flowtest-cli/graph/compute/utils.js
================================================
const computeNodeVariable = (variable, prevNodeOutputData) => {
  if (variable.type.toLowerCase() === 'string') {
    return variable.value.toString();
  }

  if (variable.type.toLowerCase() === 'number') {
    return Number(variable.value);
  }

  if (variable.type.toLowerCase() === 'boolean') {
    return Boolean(variable.value);
  }

  if (variable.type.toLowerCase() === 'now') {
    return Date.now();
  }

  if (variable.type.toLowerCase() === 'select') {
    try {
      if (prevNodeOutputData == undefined || Object.keys(prevNodeOutputData).length === 0) {
        console.debug(
          `Cannot evaluate variable ${variable} as previous node output data ${JSON.stringify(prevNodeOutputData)} is empty`,
        );
        throw Error(`Cannot evaluate variable ${variable.value}`);
      }
      const jsonTree = variable.value.split('.');
      const getVal = (parent, pos) => {
        if (pos == jsonTree.length) {
          return parent;
        }
        const key = jsonTree[pos];
        if (key == '') {
          return parent;
        }

        return getVal(parent[key], pos + 1);
      };
      const result = getVal(prevNodeOutputData, 0);
      if (result == undefined) {
        console.debug(
          `Cannot evaluate variable ${JSON.stringify(variable)} as previous node output data ${JSON.stringify(prevNodeOutputData)} did not contain the variable`,
        );
        throw Error(`Cannot evaluate variable ${variable.value}`);
      }
      return result;
    } catch (error) {
      throw Error(`Cannot evaluate variable ${variable.value}`);
    }
  }
};

const computeNodeVariables = (variables, prevNodeOutputData) => {
  const evalVariables = {};
  if (variables) {
    Object.entries(variables).map(([vname, variable]) => {
      evalVariables[vname] = computeNodeVariable(variable, prevNodeOutputData);
    });
  }
  return evalVariables;
};

const computeVariables = (str, variablesDict) => {
  const regex = /\{\{(.+)\}\}/;
  const foundRegex = regex.exec(str);
  if (foundRegex) {
    const match = str.match(/{{([^}]+)}}/);
    if (variablesDict) {
      if (Object.prototype.hasOwnProperty.call(variablesDict, match[1])) {
        const varValue = variablesDict[`${match[1]}`];
        return computeVariables(str.replaceAll(match[0], varValue), variablesDict);
      } else {
        throw Error(`Cannot find value of variable ${match[1]}`);
      }
    } else {
      throw Error(`Cannot compute variable ${match[1]} as dict is empty`);
    }
  } else {
    return str;
  }
};

module.exports = {
  computeNodeVariable,
  computeNodeVariables,
  computeVariables,
};


================================================
FILE: packages/flowtest-cli/graph/compute/utils.test.js
================================================
const { computeVariables } = require('./utils');

describe('Utils', () => {
  it('should compute variables correctly', () => {
    let str = 'hello {{var1}}! hello {{var1}}! bye';
    let dict = {
      var1: 'world',
    };

    let result = computeVariables(str, dict);
    expect(result).toEqual('hello world! hello world! bye');

    str = 'hello {{var1}}! hello {{var2}}! bye';
    expect(() => {
      computeVariables(str, dict);
    }).toThrow(Error);

    str = 'hello {{var1}}! hello {{var2}}! bye';
    dict = null;
    expect(() => {
      computeVariables(str, dict);
    }).toThrow(Error);

    dict = {
      var1: 'world',
      var2: 'person',
    };
    result = computeVariables(str, dict);
    expect(result).toEqual('hello world! hello person! bye');

    str = 'hello world!';
    result = computeVariables(str, dict);
    expect(result).toEqual(str);
  });
});


================================================
FILE: packages/flowtest-cli/graph/constants/assertOperators.js
================================================
const AssertOperators = {
  isLessThan: 'isLessThan',
  isGreaterThan: 'isGreaterThan',
  isEqualTo: 'isEqualTo',
  isNotEqualTo: 'isNotEqualTo',
};

module.exports = AssertOperators;


================================================
FILE: packages/flowtest-cli/graph/constants/evaluateOperators.js
================================================
const EvaluateOperators = {
  Add: 'Add two numbers',
  Subtract: 'Subtract two numbers',
};

module.exports = EvaluateOperators;


================================================
FILE: packages/flowtest-cli/package.json
================================================
{
  "name": "flowtestai",
  "version": "1.0.2",
  "description": "CLI to run flow from command line",
  "main": "bin/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "bin": {
    "flow": "bin/index.js"
  },
  "bugs": {
    "url": "https://github.com/FlowTestAI/FlowTest/issues"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/FlowTestAI/FlowTest.git"
  },
  "author": "Sajal Jain <jsajal1993@gmail.com>",
  "license": "MIT",
  "dependencies": {
    "axios": "^1.7.2",
    "axios-retry": "^4.4.0",
    "boxen": "^7.1.1",
    "chalk": "^3.0.0",
    "dotenv": "^16.4.5",
    "form-data": "^4.0.0",
    "fs": "^0.0.1-security",
    "lodash": "^4.17.21",
    "omelette": "^0.4.17",
    "path": "^0.12.7",
    "yargs": "^17.7.2"
  },
  "engines": {
    "node": ">=18.17.0"
  }
}


================================================
FILE: packages/flowtest-cli/utils/flowparser/AssertNode.js
================================================
const { Node } = require('./Node');

class AssertNode extends Node {
  constructor() {
    super('assertNode');
  }

  serialize(id, data, metadata) {
    return {
      id,
      type: this.type,
      data,
      ...metadata,
    };
  }

  deserialize(node) {
    const id = node.id;
    const data = node.data;
    delete node.id;
    delete node.data;
    const metadata = node;

    return {
      id,
      data,
      metadata,
    };
  }
}

module.exports = {
  AssertNode,
};


================================================
FILE: packages/flowtest-cli/utils/flowparser/AuthNode.js
================================================
const { Node } = require('./Node');

class AuthNode extends Node {
  constructor() {
    super('authNode');
  }

  serialize(id, data, metadata) {
    return {
      id,
      type: this.type,
      data,
      ...metadata,
    };
  }

  deserialize(node) {
    const id = node.id;
    const data = node.data;
    delete node.id;
    delete node.data;
    const metadata = node;

    return {
      id,
      data,
      metadata,
    };
  }
}

module.exports = {
  AuthNode,
};


================================================
FILE: packages/flowtest-cli/utils/flowparser/DelayNode.js
================================================
const { Node } = require('./Node');

class DelayNode extends Node {
  constructor() {
    super('delayNode');
  }

  serialize(id, data, metadata) {
    return {
      id,
      type: this.type,
      data,
      ...metadata,
    };
  }

  deserialize(node) {
    const id = node.id;
    const data = node.data;
    delete node.id;
    delete node.data;
    const metadata = node;

    return {
      id,
      data,
      metadata,
    };
  }
}

module.exports = {
  DelayNode,
};


================================================
FILE: packages/flowtest-cli/utils/flowparser/NestedFlowNode.js
================================================
const { Node } = require('./Node');

class NestedFlowNode extends Node {
  constructor() {
    super('flowNode');
  }

  serialize(id, data, metadata) {
    return {
      id,
      type: this.type,
      data,
      ...metadata,
    };
  }

  deserialize(node) {
    const id = node.id;
    const data = node.data;
    delete node.id;
    delete node.data;
    const metadata = node;

    return {
      id,
      data,
      metadata,
    };
  }
}

module.exports = {
  NestedFlowNode,
};


================================================
FILE: packages/flowtest-cli/utils/flowparser/Node.js
================================================
class Node {
  constructor(type) {
    this.type = type;
  }

  serialize(id, data, metadata) {
    throw new Error('Serialize method must be implemented by subclasses');
  }

  deserialize(node) {
    throw new Error('Deserialize method must be implemented by subclasses');
  }
}

module.exports = {
  Node,
};


================================================
FILE: packages/flowtest-cli/utils/flowparser/OutputNode.js
================================================
const { Node } = require('./Node');

class OutputNode extends Node {
  constructor() {
    super('outputNode');
  }

  serialize(id, data, metadata) {
    return {
      id,
      type: this.type,
      data,
      ...metadata,
    };
  }

  deserialize(node) {
    const id = node.id;
    const { ['output']: _, ...data } = node.data;
    delete node.id;
    delete node.data;
    const metadata = node;

    return {
      id,
      data,
      metadata,
    };
  }
}

module.exports = {
  OutputNode,
};


================================================
FILE: packages/flowtest-cli/utils/flowparser/RequestNode.js
================================================
const { Node } = require('./Node');

class RequestNode extends Node {
  constructor() {
    super('requestNode');
  }

  serialize(id, data, metadata) {
    return {
      id,
      type: this.type,
      data,
      ...metadata,
    };
  }

  deserialize(node) {
    const id = node.id;
    const data = node.data;
    delete node.id;
    delete node.data;
    const metadata = node;

    return {
      id,
      data,
      metadata,
    };
  }
}

module.exports = {
  RequestNode,
};


================================================
FILE: packages/flowtest-cli/utils/flowparser/SetVarNode.js
================================================
const { Node } = require('./Node');

class SetVarNode extends Node {
  constructor() {
    super('setVarNode');
  }

  serialize(id, data, metadata) {
    return {
      id,
      type: this.type,
      data,
      ...metadata,
    };
  }

  deserialize(node) {
    const id = node.id;
    const data = node.data;
    delete node.id;
    delete node.data;
    const metadata = node;

    return {
      id,
      data,
      metadata,
    };
  }
}

module.exports = {
  SetVarNode,
};


================================================
FILE: packages/flowtest-cli/utils/flowparser/StartNode.js
================================================
const { Node } = require('./Node');

class StartNode extends Node {
  constructor() {
    super('startNode');
  }

  serialize(id, data, metadata) {
    return {
      id,
      ...metadata,
    };
  }

  deserialize(node) {
    const id = node.id;
    delete node.id;
    const metadata = node;

    return {
      id,
      metadata,
    };
  }
}

module.exports = {
  StartNode,
};


================================================
FILE: packages/flowtest-cli/utils/flowparser/parser.js
================================================
const { cloneDeep } = require('lodash');
const { AuthNode } = require('./AuthNode');
const { NestedFlowNode } = require('./NestedFlowNode');
const { DelayNode } = require('./DelayNode');
const { AssertNode } = require('./AssertNode');
const { OutputNode } = require('./OutputNode');
const { RequestNode } = require('./RequestNode');
const { StartNode } = require('./StartNode');
const { SetVarNode } = require('./SetVarNode');

const VERSION = 1;

const deserialize = (flowData) => {
  // we don't want to modify original object
  const flowDataCopy = cloneDeep(flowData);

  const textData = {};
  textData.version = VERSION;
  textData.graph = {};

  if (flowData) {
    if (flowData.nodes) {
      const nodes = flowDataCopy.nodes;
      textData.graph.data = {};
      textData.graph.data.nodes = {};
      textData.graph.metadata = {};
      textData.graph.metadata.nodes = {};

      nodes.forEach((node) => {
        if (node.type === 'startNode') {
          const sNode = new StartNode();
          const result = sNode.deserialize(node);

          textData.graph.data.nodes[result.id] = {
            type: 'startNode',
          };

          textData.graph.metadata.nodes[result.id] = {
            type: 'startNode',
            ...result.metadata,
          };
        }

        if (node.type === 'authNode') {
          const aNode = new AuthNode();
          const result = aNode.deserialize(node);
          textData.graph.data.nodes[result.id] = {
            type: 'authNode',
            auth: result.data,
          };

          textData.graph.metadata.nodes[result.id] = {
            type: 'authNode',
            ...result.metadata,
          };
        }

        if (node.type === 'requestNode') {
          const rNode = new RequestNode();
          const result = rNode.deserialize(node);
          textData.graph.data.nodes[result.id] = {
            type: 'requestNode',
            ...result.data,
          };

          textData.graph.metadata.nodes[result.id] = {
            type: 'requestNode',
            ...result.metadata,
          };
        }

        if (node.type === 'outputNode') {
          const oNode = new OutputNode();
          const result = oNode.deserialize(node);
          textData.graph.data.nodes[result.id] = {
            type: 'outputNode',
            ...result.data,
          };

          textData.graph.metadata.nodes[result.id] = {
            type: 'outputNode',
            ...result.metadata,
          };
        }

        if (node.type === 'delayNode') {
          const dNode = new DelayNode();
          const result = dNode.deserialize(node);
          textData.graph.data.nodes[result.id] = {
            type: 'delayNode',
            ...result.data,
          };

          textData.graph.metadata.nodes[result.id] = {
            type: 'delayNode',
            ...result.metadata,
          };
        }

        if (node.type === 'assertNode') {
          const eNode = new AssertNode();
          const result = eNode.deserialize(node);
          textData.graph.data.nodes[result.id] = {
            type: 'assertNode',
            ...result.data,
          };

          textData.graph.metadata.nodes[result.id] = {
            type: 'assertNode',
            ...result.metadata,
          };
        }

        if (node.type === 'flowNode') {
          const fNode = new NestedFlowNode();
          const result = fNode.deserialize(node);
          textData.graph.data.nodes[result.id] = {
            type: 'flowNode',
            ...result.data,
          };

          textData.graph.metadata.nodes[result.id] = {
            type: 'flowNode',
            ...result.metadata,
          };
        }

        if (node.type === 'setVarNode') {
          const sNode = new SetVarNode();
          const result = sNode.deserialize(node);
          textData.graph.data.nodes[result.id] = {
            type: 'setVarNode',
            ...result.data,
          };

          textData.graph.metadata.nodes[result.id] = {
            type: 'setVarNode',
            ...result.metadata,
          };
        }
      });
    }

    if (flowData.edges) {
      const edges = flowDataCopy.edges;
      textData.graph.data.edges = [];
      textData.graph.metadata.edges = {};

      edges.forEach((edge) => {
        textData.graph.data.edges.push(`${edge.source} -> ${edge.target}`);

        const { ['id']: _, ..._edge } = edge;
        textData.graph.metadata.edges[edge.id] = _edge;
      });
    }

    if (flowData.viewport) {
      textData.graph.metadata.viewport = flowDataCopy.viewport;
    }
  }

  return textData;
};

const serialize = (textData) => {
  const flowData = {};
  flowData.nodes = [];
  flowData.edges = [];
  flowData.viewport = { x: 0, y: 0, zoom: 1 };

  // we don't want to modify original object
  const textDataCopy = cloneDeep(textData);
  const version = textDataCopy.version;
  if (version === 1) {
    if (textDataCopy.graph.data) {
      Object.entries(textDataCopy.graph.data.nodes).map(([key, value], index) => {
        const id = key;

        if (value.type === 'startNode') {
          const metadata = textDataCopy.graph.metadata.nodes[id];
          const sNode = new StartNode();
          const result = sNode.serialize(id, undefined, metadata);

          flowData.nodes.push(result);
        }

        if (value.type === 'authNode') {
          const data = value.auth;
          const metadata = textDataCopy.graph.metadata.nodes[id];
          const aNode = new AuthNode();
          const result = aNode.serialize(id, data, metadata);
          flowData.nodes.push(result);
        }

        if (value.type === 'requestNode') {
          const data = value;
          const metadata = textDataCopy.graph.metadata.nodes[id];
          const rNode = new RequestNode();
          const result = rNode.serialize(id, data, metadata);
          flowData.nodes.push(result);
        }

        if (value.type === 'outputNode') {
          const data = value;
          const metadata = textDataCopy.graph.metadata.nodes[id];
          const oNode = new OutputNode();
          const result = oNode.serialize(id, data, metadata);
          flowData.nodes.push(result);
        }

        if (value.type === 'delayNode') {
          const data = value;
          const metadata = textDataCopy.graph.metadata.nodes[id];
          const dNode = new DelayNode();
          const result = dNode.serialize(id, data, metadata);
          flowData.nodes.push(result);
        }

        if (value.type === 'assertNode') {
          const data = value;
          const metadata = textDataCopy.graph.metadata.nodes[id];
          const dNode = new AssertNode();
          const result = dNode.serialize(id, data, metadata);
          flowData.nodes.push(result);
        }

        if (value.type === 'flowNode') {
          const data = value;
          const metadata = textDataCopy.graph.metadata.nodes[id];
          const fNode = new NestedFlowNode();
          const result = fNode.serialize(id, data, metadata);
          flowData.nodes.push(result);
        }

        if (value.type === 'setVarNode') {
          const data = value;
          const metadata = textDataCopy.graph.metadata.nodes[id];
          const cNode = new SetVarNode();
          const result = cNode.serialize(id, data, metadata);
          flowData.nodes.push(result);
        }
      });

      Object.entries(textDataCopy.graph.metadata.edges).map(([key, value], index) => {
        flowData.edges.push({
          id: key,
          ...value,
        });
      });

      if (textDataCopy.graph.metadata.viewport) {
        flowData.viewport = textDataCopy.graph.metadata.viewport;
      }
    }
  } else {
    throw new Error('Version not recognized');
  }

  return flowData;
};

module.exports = {
  deserialize,
  serialize,
};


================================================
FILE: packages/flowtest-cli/utils/readfile.js
================================================
const fs = require('fs');

const pathExists = (path) => {
  try {
    fs.accessSync(path);
    return true;
  } catch (error) {
    return false;
  }
};

const readFile = (path) => {
  if (!path) {
    throw new Error('File path is required');
  }

  // check if file exists
  if (!pathExists(path)) {
    throw new Error('File does not exist');
  }

  // now delete the file
  return fs.readFileSync(path, 'utf8');
};

module.exports = readFile;


================================================
FILE: packages/flowtest-electron/.npmrc
================================================
node-linker=hoisted #pnpm requirement when working with electron

================================================
FILE: packages/flowtest-electron/CHANGELOG.md
================================================
# flowtestai

## 1.2.0

### Minor Changes

- aadbd1b: Collapse sidebar to give more real estate to canvas
- 780f3c2: generate sample request body and parameter values
- e968f1e: beautify logs sidesheet and ability to upload flow scans
- 92a0bab: Add support for anthropic claude hosted on bedrock
- 97c6981: display pretty structured logs in graph run
- 6def317: check version mismatch on startup and notify user of latest version availability
- 5a92053: Ability to manage multiple flow tabs simultaneously
- 563e011: Allow multiple kv params in multipart form data request type
- a07dbea: allow configurable user settings
- d20fb8b: make app platform agnostic to allow windows platform support
- 912483f: use rich editor for auto complete variables and redefine UI of request nodes

## 1.1.0

### Minor Changes

- a5d61a8: Introduce a UI theme, json editor powered by codemirror and few shortcut keys to improve workflow
- 7f45adc: A more intuitive UX to onboard first time user
- 95310af: Updated contributing docs and linting
- 19c52aa: Ability to clone a fow and expand output node for bigger view
- 5ac8237: Maintain state of logs and viewports of each canvas separately.


================================================
FILE: packages/flowtest-electron/electron-main.js
================================================
// Modules to control application life and create native browser window
const { app, BrowserWindow, Menu, shell } = require('electron');
const path = require('path');
const url = require('url');
const template = require('./electron-menu');
const Watcher = require('./src/app/watcher');
const registerRendererEventHandlers = require('./src/ipc/collection');
const registerSettingsEventHandlers = require('./src/ipc/settings');
const packageJson = require('./package.json'); // app's package.json
const https = require('https');

let mainWindow;
let watcher;

if (process.env.NODE_ENV === 'production') {
  const noop = () => {};
  console.log = noop;
  console.info = noop;
  console.error = noop;
  console.warn = noop;
  console.debug = noop;
  console.trace = noop;
}

const version = {
  current: packageJson.version,
  latest: packageJson.version,
};

function checkForUpdates() {
  const url = `https://raw.githubusercontent.com/FlowTestAI/FlowTest/main/packages/flowtest-electron/package.json`;

  https
    .get(url, (res) => {
      let data = '';

      res.on('data', (chunk) => {
        data += chunk;
      });

      res.on('end', () => {
        try {
          const remotePackageJson = JSON.parse(data);
          const latestVersion = remotePackageJson.version;

          if (latestVersion !== version.current) {
            version.latest = latestVersion;
            //shell.openExternal(`https://github.com/${username}/${repo}/releases`);
          }
        } catch (error) {
          console.error('Error parsing JSON:', error);
        }
      });
    })
    .on('error', (err) => {
      console.error('Error fetching package.json:', err);
    });
}

app.on('ready', async () => {
  const menu = Menu.buildFromTemplate(template);
  Menu.setApplicationMenu(menu);

  // Create the browser window.
  mainWindow = new BrowserWindow({
    width: 1280,
    height: 768,
    icon: path.join(__dirname, 'assets/MyIcon.png'),
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: true,
      preload: path.join(__dirname, 'preload.js'),
      webviewTag: true,
    },
    title: 'FlowTestAI',
  });

  mainWindow.maximize();

  // and load the index.html of the app.
  const startUrl = url.format({
    pathname: path.join(__dirname, '../../build/index.html'),
    protocol: 'file:',
    slashes: true,
  });
  mainWindow.loadURL(startUrl);

  // Open the DevTools.
  // mainWindow.webContents.openDevTools()

  // This is required to open a link in the external browser
  mainWindow.webContents.setWindowOpenHandler(({ url }) => {
    shell.openExternal(url);
    return { action: 'deny' };
  });

  watcher = new Watcher();

  checkForUpdates();
  mainWindow.webContents.on('did-finish-load', () => {
    // Send a message to the renderer process
    mainWindow.webContents.send('main:app-version', version);
  });

  registerRendererEventHandlers(mainWindow, watcher);
  registerSettingsEventHandlers(mainWindow);
});

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
  //if (process.platform !== 'darwin')
  app.quit();
});


================================================
FILE: packages/flowtest-electron/electron-menu.js
================================================
const { shell } = require('electron');

const template = [
  {
    label: 'FlowTestAI',
    submenu: [
      { type: 'separator' },
      {
        role: 'quit',
        label: 'Exit FlowTestAI',
      },
    ],
  },
  {
    label: 'Edit',
    submenu: [
      { role: 'undo' },
      { role: 'redo' },
      { type: 'separator' },
      { role: 'cut' },
      { role: 'copy' },
      { role: 'paste' },
      { role: 'selectAll' },
      { type: 'separator' },
      { role: 'hide' },
      { role: 'hideOthers' },
    ],
  },
  {
    label: 'View',
    submenu: [
      { role: 'toggledevtools' },
      { type: 'separator' },
      { role: 'resetzoom' },
      { role: 'zoomin' },
      { role: 'zoomout' },
      { type: 'separator' },
      { role: 'togglefullscreen' },
    ],
  },
  {
    role: 'window',
    submenu: [{ role: 'minimize' }, { role: 'close', accelerator: 'CommandOrControl+Shift+Q' }],
  },
  {
    role: 'help',
    label: 'Help',
    submenu: [
      {
        label: 'About',
        click: async () => {
          await shell.openExternal('https://github.com/FlowTestAI/FlowTest');
        },
      },
    ],
  },
];

module.exports = template;


================================================
FILE: packages/flowtest-electron/notarize.js
================================================
require('dotenv').config();
const { notarize } = require('@electron/notarize');

exports.default = async function notarizing(context) {
  const { electronPlatformName, appOutDir } = context;
  if (electronPlatformName !== 'darwin') {
    return;
  }

  const appName = context.packager.appInfo.productFilename;

  return await notarize({
    appBundleId: 'com.flowtestai.app',
    appPath: `${appOutDir}/${appName}.app`,
    appleId: process.env.APPLE_ID,
    appleIdPassword: process.env.APPLE_ID_PASSWORD,
    teamId: process.env.TEAM_ID,
  });
};


================================================
FILE: packages/flowtest-electron/package.json
================================================
{
  "name": "flowtestai-app",
  "productName": "FlowTestAI",
  "version": "1.2.0",
  "homepage": "https://github.com/FlowTestAI/FlowTest/tree/main",
  "description": "GenAI powered OpenSource IDE for API first workflows",
  "main": "electron-main.js",
  "bugs": {
    "url": "https://github.com/FlowTestAI/FlowTest/issues"
  },
  "engines": {
    "node": ">=18.17.0"
  },
  "scripts": {
    "start": "electron .",
    "test": "jest",
    "pack": "NODE_ENV=production electron-builder --dir",
    "dist:mac": "NODE_ENV=production electron-builder --mac",
    "dist:win": "SET NODE_ENV=production & electron-builder --win"
  },
  "author": "Sajal Jain <jsajal1993@gmail.com>",
  "license": "MIT",
  "devDependencies": {
    "@electron/notarize": "^2.3.0",
    "electron": "^29.0.0",
    "electron-builder": "^24.13.3",
    "jest": "^29.7.0"
  },
  "dependencies": {
    "@apidevtools/swagger-parser": "^10.1.0",
    "@aws-crypto/sha256-js": "^5.2.0",
    "@aws-sdk/client-bedrock": "^3.583.0",
    "@aws-sdk/client-bedrock-runtime": "^3.583.0",
    "@aws-sdk/credential-provider-node": "^3.583.0",
    "@aws-sdk/types": "^3.577.0",
    "@google/generative-ai": "^0.16.0",
    "@langchain/community": "^0.2.19",
    "@langchain/google-genai": "^0.0.25",
    "@smithy/eventstream-codec": "^3.0.0",
    "@smithy/protocol-http": "^4.0.0",
    "@smithy/signature-v4": "^3.0.0",
    "@smithy/util-utf8": "^3.0.0",
    "axios": "^1.6.7",
    "axios-retry": "^4.4.0",
    "chokidar": "^3.6.0",
    "dotenv": "^16.4.5",
    "electron-store": "^8.1.0",
    "flatted": "^3.3.1",
    "form-data": "^4.0.0",
    "fs": "^0.0.1-security",
    "json-refs": "^3.0.15",
    "langchain": "^0.1.28",
    "lodash": "^4.17.21",
    "openai": "^4.29.1",
    "path": "^0.12.7",
    "uuid": "^9.0.1"
  },
  "build": {
    "appId": "com.flowtestai.app",
    "productName": "FlowTestAI",
    "directories": {
      "buildResources": "resources",
      "output": "dist"
    },
    "files": [
      "**/*"
    ],
    "afterSign": "notarize.js",
    "win": {
      "target": "nsis"
    },
    "mac": {
      "target": {
        "target": "default",
        "arch": [
          "x64",
          "arm64"
        ]
      },
      "category": "public.app-category.developer-tools",
      "identity": "Sajal Jain (Z25C545DT5)",
      "hardenedRuntime": true,
      "gatekeeperAssess": false,
      "icon": "assets/MyIcon.icns"
    },
    "linux": {
      "target": [
        "AppImage",
        "deb"
      ]
    }
  }
}


================================================
FILE: packages/flowtest-electron/preload.js
================================================
const { ipcRenderer, contextBridge } = require('electron');
const path = require('path');
const { isMacOS } = require('./src/utils/filemanager/filesystem');

contextBridge.exposeInMainWorld('ipcRenderer', {
  invoke: (channel, ...args) => ipcRenderer.invoke(channel, ...args),
  on: (channel, handler) => ipcRenderer.on(channel, (event, ...args) => handler(...args)),
  join: (...args) => path.join(...args),
  relative: (...args) => path.relative(...args),
  dirname: (...args) => path.dirname(...args),
  isMacOs: isMacOS,
});


================================================
FILE: packages/flowtest-electron/src/ai/flowtestai.js
================================================
const BedrockClaudeGenerate = require('./models/bedrock_claude');
const GeminiGenerate = require('./models/gemini');
const OpenAIGenerate = require('./models/openai');

class FlowtestAI {
  async generate(collection, user_instruction, model) {
    if (model.name === 'OPENAI') {
      const available_functions = await this.get_available_functions(collection);
      const openai = new OpenAIGenerate();
      const functions = await openai.filter_functions(available_functions, user_instruction, model.apiKey);
      return await openai.process_user_instruction(functions, user_instruction, model.apiKey);
    } else if (model.name === 'BEDROCK_CLAUDE') {
      const available_functions = await this.get_available_functions(collection);
      const bedrock_claude = new BedrockClaudeGenerate(model.apiKey);
      const functions = await bedrock_claude.filter_functions(available_functions, user_instruction);
      return await bedrock_claude.process_user_instruction(functions, user_instruction);
    } else if (model.name === 'GEMINI') {
      const available_functions = await this.get_available_functions(collection);
      const gemini = new GeminiGenerate(model.apiKey);
      const functions = await gemini.filter_functions(available_functions, user_instruction);
      return await gemini.process_user_instruction(functions, user_instruction);
    } else {
      throw Error(`Model ${model.name} not supported`);
    }
  }

  async get_available_functions(collection) {
    let functions = [];
    Object.entries(collection['paths']).map(([path, methods], index) => {
      Object.entries(methods).map(([method, spec], index1) => {
        const function_name = spec['operationId'];

        const desc = spec['description'] || spec['summary'] || '';

        let schema = { type: 'object', properties: {} };

        let req_body = undefined;
        if (spec['requestBody']) {
          if (spec['requestBody']['content']) {
            if (spec['requestBody']['content']['application/json']) {
              if (spec['requestBody']['content']['application/json']['schema']) {
                req_body = spec['requestBody']['content']['application/json']['schema'];
              }
            }
          }
        }

        if (req_body != undefined) {
          schema['properties']['requestBody'] = req_body;
        }

        const params = spec['parameters'] ? spec['parameters'] : [];
        const param_properties = {};
        if (params.length > 0) {
          for (const param of params) {
            if (param['schema']) {
              param_properties[param['name']] = param['schema'];
            }
          }
          schema['properties']['parameters'] = {
            type: 'object',
            properties: param_properties,
          };
        }

        const f = {
          type: 'function',
          function: { name: function_name, description: desc, parameters: schema },
        };

        if (this.isCyclic(f)) {
          functions.push({
            type: 'function',
            function: { name: function_name, description: desc, parameters: {} },
          });
        } else {
          functions.push(f);
        }
      });
    });

    return functions;
  }

  isCyclic(obj) {
    var keys = [];
    var stack = [];
    var stackSet = new Set();
    var detected = false;

    function detect(obj, key) {
      if (obj && typeof obj != 'object') {
        return false;
      }

      if (stackSet.has(obj)) {
        // it's cyclic! Print the object and its locations.
        var oldindex = stack.indexOf(obj);
        var l1 = keys.join('.') + '.' + key;
        var l2 = keys.slice(0, oldindex + 1).join('.');
        //console.log('CIRCULAR: ' + l1 + ' = ' + l2 + ' = ' + obj);
        //console.log(obj);
        detected = true;
        return;
      }

      keys.push(key);
      stack.push(obj);
      stackSet.add(obj);
      for (var k in obj) {
        //dive on the object's children
        if (Object.prototype.hasOwnProperty.call(obj, k)) {
          detect(obj[k], k);
        }
      }

      keys.pop();
      stack.pop();
      stackSet.delete(obj);
      return;
    }

    detect(obj, 'obj', keys, stack, stackSet, detected);
    return detected;
  }
}

module.exports = FlowtestAI;


================================================
FILE: packages/flowtest-electron/src/ai/models/bedrock_claude.js
================================================
const { BedrockChat } = require('@langchain/community/chat_models/bedrock');
const { HumanMessage, SystemMessage, BaseMessage } = require('@langchain/core/messages');
const { BedrockEmbeddings } = require('@langchain/community/embeddings/bedrock');
const { MemoryVectorStore } = require('langchain/vectorstores/memory');

class BedrockClaudeGenerate {
  constructor(creds) {
    this.model = new BedrockChat({
      model: 'anthropic.claude-3-sonnet-20240229-v1:0',
      region: 'us-west-2',
      // endpointUrl: "custom.amazonaws.com",
      credentials: creds,
      modelKwargs: {
        anthropic_version: 'bedrock-2023-05-31',
      },
    });

    this.embeddings = new BedrockEmbeddings({
      region: 'us-west-2',
      credentials: creds,
      model: 'amazon.titan-embed-text-v2:0', // Default value
    });
  }

  async filter_functions(functions, instruction) {
    const documents = functions.map((f) => {
      const { parameters, ...fDescription } = f.function;
      return JSON.stringify(fDescription);
    });

    const vectorStore = await MemoryVectorStore.fromTexts(documents, [], this.embeddings);
    // 128 (max no of functions accepted by openAI function calling)
    const retrievedDocuments = await vectorStore.similaritySearch(instruction, 10);
    var selectedFunctions = [];
    retrievedDocuments.forEach((document) => {
      const pDocument = JSON.parse(document.pageContent);
      const findF = functions.find(
        (f) => f.function.name === pDocument.name && f.function.description === pDocument.description,
      );
      if (findF) {
        selectedFunctions = selectedFunctions.concat(findF);
      }
    });
    return selectedFunctions;
  }

  async process_user_instruction(functions, instruction) {
    //console.log(functions.map((f) => f.function.name));
    // Define the function call format
    const fn = `{"name": "function_name"}`;

    // Prepare the function string for the system prompt
    const fnStr = functions.map((f) => JSON.stringify(f)).join('\n');

    // Define the system prompt
    const systemPrompt = `
        You are a helpful assistant with access to the following functions:

        ${fnStr}

        To use these functions respond with, only output function names, ignore arguments needed by those functions:

        <multiplefunctions>
            <functioncall> ${fn} </functioncall>
            <functioncall> ${fn} </functioncall>
            ...
        </multiplefunctions>

        Edge cases you must handle:
        - If there are multiple functions that can fullfill user request, list them all.
        - If there are no functions that match the user request, you will respond politely that you cannot help.
        - If the user has not provided all information to execute the function call, choose the best possible set of values. Only, respond with the information requested and nothing else.
        - If asked something that cannot be determined with the user's request details, respond that it is not possible to fulfill the request and explain why.
    `;

    // Prepare the messages for the language model
    const messages = [new SystemMessage({ content: systemPrompt }), new HumanMessage({ content: instruction })];

    // Invoke the language model and get the completion
    const completion = await this.model.invoke(messages);
    const content = completion.content.trim();

    // Extract function calls from the completion
    const extractedFunctions = this.extractFunctionCalls(content);

    console.log(extractedFunctions);

    return extractedFunctions;
  }

  extractFunctionCalls(completion) {
    let content = typeof completion === 'string' ? completion : completion.content;

    // Multiple functions lookup
    const mfnPattern = /<multiplefunctions>(.*?)<\/multiplefunctions>/s;
    const mfnMatch = content.match(mfnPattern);

    // Single function lookup
    const singlePattern = /<functioncall>(.*?)<\/functioncall>/s;
    const singleMatch = content.match(singlePattern);

    let functions = [];

    if (!mfnMatch && !singleMatch) {
      // No function calls found
      return null;
    } else if (mfnMatch) {
      // Multiple function calls found
      const multiplefn = mfnMatch[1];
      const fnMatches = [...multiplefn.matchAll(/<functioncall>(.*?)<\/functioncall>/gs)];
      for (let fnMatch of fnMatches) {
        const fnText = fnMatch[1].replace(/\\/g, '');
        try {
          functions.push(JSON.parse(fnText));
        } catch {
          // Ignore invalid JSON
        }
      }
    } else {
      // Single function call found
      const fnText = singleMatch[1].replace(/\\/g, '');
      try {
        functions.push(JSON.parse(fnText));
      } catch {
        // Ignore invalid JSON
      }
    }
    return functions;
  }
}

module.exports = BedrockClaudeGenerate;


================================================
FILE: packages/flowtest-electron/src/ai/models/gemini.js
================================================
const { GoogleGenerativeAI } = require('@google/generative-ai');
const { GoogleGenerativeAIEmbeddings } = require('@langchain/google-genai');
const { TaskType } = require('@google/generative-ai');
const { MemoryVectorStore } = require('langchain/vectorstores/memory');

class GeminiGenerate {
  constructor(apiKey) {
    this.genAI = new GoogleGenerativeAI(apiKey);

    this.embeddings = new GoogleGenerativeAIEmbeddings({
      apiKey,
      model: 'text-embedding-004', // 768 dimensions
      taskType: TaskType.RETRIEVAL_DOCUMENT,
      title: 'Document title',
    });
  }

  async filter_functions(functions, instruction) {
    const documents = functions.map((f) => {
      const { parameters, ...fDescription } = f.function;
      return JSON.stringify(fDescription);
    });

    const vectorStore = await MemoryVectorStore.fromTexts(documents, [], this.embeddings);

    // 128 (max no of functions accepted by openAI function calling)
    const retrievedDocuments = await vectorStore.similaritySearch(instruction, 10);
    var selectedFunctions = [];
    retrievedDocuments.forEach((document) => {
      const pDocument = JSON.parse(document.pageContent);
      const findF = functions.find(
        (f) => f.function.name === pDocument.name && f.function.description === pDocument.description,
      );
      if (findF) {
        selectedFunctions = selectedFunctions.concat(findF);
      }
    });

    return selectedFunctions;
  }

  async process_user_instruction(functions, instruction) {
    //console.log(functions.map((f) => f.function.name));
    // Define the function call format
    const fn = `{"name": "function_name"}`;

    // Prepare the function string for the system prompt
    const fnStr = functions.map((f) => JSON.stringify(f)).join('\n');

    // Define the system prompt
    const systemPrompt = `
        You are a helpful assistant with access to the following functions:

        ${fnStr}

        To use these functions respond with, only output function names, ignore arguments needed by those functions:

        <multiplefunctions>
            <functioncall> ${fn} </functioncall>
            <functioncall> ${fn} </functioncall>
            ...
        </multiplefunctions>

        Edge cases you must handle:
        - If there are multiple functions that can fullfill user request, list them all.
        - If there are no functions that match the user request, you will respond politely that you cannot help.
        - If the user has not provided all information to execute the function call, choose the best possible set of values. Only, respond with the information requested and nothing else.
        - If asked something that cannot be determined with the user's request details, respond that it is not possible to fulfill the request and explain why.
    `;

    const model = this.genAI.getGenerativeModel({
      model: 'gemini-1.5-pro-latest',
      systemInstruction: {
        role: 'system',
        parts: [{ text: systemPrompt }],
      },
    });

    // Prepare the messages for the language model

    const request = {
      contents: [{ role: 'user', parts: [{ text: instruction }] }],
    };

    // Invoke the language model and get the completion
    const completion = await model.generateContent(request);

    const content = completion.response.candidates[0].content.parts[0].text.trim();

    // Extract function calls from the completion
    const extractedFunctions = this.extractFunctionCalls(content);

    return extractedFunctions;
  }

  extractFunctionCalls(completion) {
    let content = typeof completion === 'string' ? completion : completion.content;

    // Multiple functions lookup
    const mfnPattern = /<multiplefunctions>(.*?)<\/multiplefunctions>/s;
    const mfnMatch = content.match(mfnPattern);

    // Single function lookup
    const singlePattern = /<functioncall>(.*?)<\/functioncall>/s;
    const singleMatch = content.match(singlePattern);

    let functions = [];

    if (!mfnMatch && !singleMatch) {
      // No function calls found
      return null;
    } else if (mfnMatch) {
      // Multiple function calls found
      const multiplefn = mfnMatch[1];
      const fnMatches = [...multiplefn.matchAll(/<functioncall>(.*?)<\/functioncall>/gs)];
      for (let fnMatch of fnMatches) {
        const fnText = fnMatch[1].replace(/\\/g, '');
        try {
          functions.push(JSON.parse(fnText));
        } catch {
          // Ignore invalid JSON
        }
      }
    } else {
      // Single function call found
      const fnText = singleMatch[1].replace(/\\/g, '');
      try {
        functions.push(JSON.parse(fnText));
      } catch {
        // Ignore invalid JSON
      }
    }
    return functions;
  }
}

module.exports = GeminiGenerate;


================================================
FILE: packages/flowtest-electron/src/ai/models/openai.js
================================================
const OpenAI = require('openai');
const { MemoryVectorStore } = require('langchain/vectorstores/memory');
const { OpenAIEmbeddings } = require('@langchain/openai');

const SYSTEM_MESSAGE = `You are a helpful assistant. \ 
        Respond to the following prompt by using function_call and then summarize actions. \ 
        If a user request is ambiguous, choose the best response possible.`;

// Maximum number of function calls allowed to prevent infinite or lengthy loops
const MAX_CALLS = 10;

class OpenAIGenerate {
  async filter_functions(functions, instruction, apiKey) {
    const documents = functions.map((f) => {
      const { parameters, ...fDescription } = f.function;
      return JSON.stringify(fDescription);
    });

    const vectorStore = await MemoryVectorStore.fromTexts(
      documents,
      [],
      new OpenAIEmbeddings({
        openAIApiKey: apiKey,
      }),
    );

    // 128 (max no of functions accepted by openAI function calling)
    const retrievedDocuments = await vectorStore.similaritySearch(instruction, 10);
    var selectedFunctions = [];
    retrievedDocuments.forEach((document) => {
      const pDocument = JSON.parse(document.pageContent);
      const findF = functions.find(
        (f) => f.function.name === pDocument.name && f.function.description === pDocument.description,
      );
      if (findF) {
        selectedFunctions = selectedFunctions.concat(findF);
      }
    });

    return selectedFunctions;
  }

  async get_openai_response(functions, messages, apiKey) {
    const openai = new OpenAI({
      apiKey,
    });

    return await openai.chat.completions.create({
      model: 'gpt-4', //gpt-3.5-turbo-16k-0613
      tools: functions,
      tool_choice: 'auto', // "auto" means the model can pick between generating a message or calling a function.
      temperature: 0,
      messages: messages,
    });
  }

  async process_user_instruction(functions, instruction, apiKey) {
    //console.log(functions.map((f) => f.function.name));
    let result = [];
    let num_calls = 0;
    const messages = [
      { content: SYSTEM_MESSAGE, role: 'system' },
      { content: instruction, role: 'user' },
    ];

    while (num_calls < MAX_CALLS) {
      const response = await this.get_openai_response(functions, messages, apiKey);
      const message = response['choices'][0]['message'];

      if (message.tool_calls) {
        messages.push(message);
        message.tool_calls.map((tool_call) => {
          console.log('Function call #: ', num_calls + 1);
          console.log(JSON.stringify(tool_call));

          // We'll simply add a message to simulate successful function call.
          messages.push({
            role: 'tool',
            content: 'success',
            tool_call_id: tool_call.id,
          });
          result.push(tool_call.function);

          num_calls += 1;
        });
      } else {
        console.log('Message: ');
        console.log(message['content']);
        break;
      }
    }

    if (num_calls >= MAX_CALLS) {
      console.log('Reached max chained function calls: ', MAX_CALLS);
    }

    return result;
  }
}

module.exports = OpenAIGenerate;


================================================
FILE: packages/flowtest-electron/src/app/watcher.js
================================================
const chokidar = require('chokidar');
const path = require('path');
const dotenv = require('dotenv');
const { PATH_SEPARATOR, getSubdirectoriesFromRoot } = require('../utils/filemanager/filesystem');
const readFile = require('../utils/filemanager/readfile');
const { serialize } = require('../utils/flowparser/parser');

class Watcher {
  constructor() {
    this.watchers = {};
  }

  isFlowTestFile(pathname) {
    if (!pathname || typeof pathname !== 'string') return false;
    return ['flow'].some((ext) => pathname.toLowerCase().endsWith(`.${ext}`));
  }

  isEnvFile(pathname, collectionPath) {
    if (!pathname || typeof pathname !== 'string') return false;
    const dirname = path.dirname(pathname);
    const envDirectory = path.join(collectionPath, 'environments');

    return dirname === envDirectory && ['env'].some((ext) => pathname.toLowerCase().endsWith(`.${ext}`));
  }

  isDotEnvFile(pathname, collectionPath) {
    const dirname = path.dirname(pathname);
    const basename = path.basename(pathname);

    return dirname === collectionPath && basename === '.env';
  }

  add(mainWindow, pathname, collectionId, watchPath) {
    console.log(`[Watcher] File ${pathname} added`);
    if (this.isFlowTestFile(pathname)) {
      const content = readFile(pathname);
      const flowData = serialize(JSON.parse(content));
      const dirname = path.dirname(pathname);
      const subDirectories = getSubdirectoriesFromRoot(watchPath, dirname);
      const file = {
        name: path.basename(pathname),
        pathname: pathname,
        subDirectories,
        sep: PATH_SEPARATOR,
        flowData,
      };
      mainWindow.webContents.send('main:create-flowtest', file, collectionId);
    } else if (this.isEnvFile(pathname, watchPath)) {
      try {
        const variables = this.getEnvVariables(pathname);
        const file = {
          name: path.basename(pathname),
          pathname: pathname,
          variables,
        };
        mainWindow.webContents.send('main:addOrUpdate-environment', file, collectionId);
      } catch (error) {
        console.error(`Failed to add ${pathname} due to: ${error}`);
      }
    } else if (this.isDotEnvFile(pathname, watchPath)) {
      try {
        const variables = this.getEnvVariables(pathname);
        mainWindow.webContents.send('main:addOrUpdate-dotEnvironment', variables, collectionId);
      } catch (error) {
        console.error(`Failed to add .env variables due to: ${error}`);
      }
    }
  }

  addDirectory(mainWindow, pathname, collectionId, watchPath) {
    const envDirectory = path.join(watchPath, 'environments');

    if (pathname === envDirectory) {
      return;
    }

    if (pathname === watchPath) {
      // we have already added collection object to store
      return;
    }

    console.log(`[Watcher] Directory ${pathname} added`);
    const directory = {
      name: path.basename(pathname),
      pathname: pathname,
    };

    const subDirsFromRoot = getSubdirectoriesFromRoot(watchPath, directory.pathname);
    mainWindow.webContents.send('main:add-directory', directory, collectionId, subDirsFromRoot, PATH_SEPARATOR);
  }

  change(mainWindow, pathname, collectionId, watchPath) {
    console.log(`[Watcher] file ${pathname} changed`);
    if (this.isFlowTestFile(pathname)) {
      const content = readFile(pathname);
      const flowData = serialize(JSON.parse(content));
      const file = {
        name: path.basename(pathname),
        pathname,
        flowData,
      };
      mainWindow.webContents.send('main:update-flowtest', file, collectionId);
    } else if (this.isEnvFile(pathname, watchPath)) {
      try {
        const variables = this.getEnvVariables(pathname);
        const file = {
          name: path.basename(pathname),
          pathname: pathname,
          variables,
        };
        mainWindow.webContents.send('main:addOrUpdate-environment', file, collectionId);
      } catch (error) {
        console.error(`Failed to save ${pathname} due to: ${error}`);
      }
    } else if (this.isDotEnvFile(pathname, watchPath)) {
      try {
        const variables = this.getEnvVariables(pathname);
        mainWindow.webContents.send('main:addOrUpdate-dotEnvironment', variables, collectionId);
      } catch (error) {
        console.error(`Failed to add .env variables due to: ${error}`);
      }
    }
  }

  unlink(mainWindow, pathname, collectionId, watchPath) {
    console.log(`[Watcher] File ${pathname} removed`);
    if (this.isFlowTestFile(pathname)) {
      const file = {
        name: path.basename(pathname),
        pathname: pathname,
      };
      mainWindow.webContents.send('main:delete-flowtest', file, collectionId);
    } else if (this.isEnvFile(pathname, watchPath)) {
      try {
        const file = {
          name: path.basename(pathname),
          pathname: pathname,
        };
        mainWindow.webContents.send('main:delete-environment', file, collectionId);
      } catch (error) {
        console.error(`Failed to save ${pathname} due to: ${error}`);
      }
    }
  }

  unlinkDir(mainWindow, pathname, collectionId, watchPath) {
    const envDirectory = path.join(watchPath, 'environments');

    if (pathname === envDirectory) {
      return;
    }

    console.log(`[Watcher] Directory ${pathname} removed`);
    const directory = {
      name: path.basename(pathname),
      pathname: pathname,
    };
    mainWindow.webContents.send('main:delete-directory', directory, collectionId);
  }

  getEnvVariables(pathname) {
    const content = readFile(pathname);
    const buf = Buffer.from(content);
    const parsed = dotenv.parse(buf);
    return parsed;
  }

  addWatcher(mainWindow, watchPath, collectionId) {
    if (!this.hasWatcher(watchPath)) {
      console.log(`[Watcher] watcher added for path: ${watchPath} `);
      if (this.watchers[watchPath]) {
        this.watchers[watchPath].close();
      }

      setTimeout(() => {
        const watcher = chokidar.watch(watchPath, {
          ignoreInitial: false,
          usePolling: watchPath.startsWith('\\\\') ? true : false,
          ignored: (path) => ['node_modules', '.git'].some((s) => path.includes(s)),
          persistent: true,
          ignorePermissionErrors: true,
          awaitWriteFinish: {
            stabilityThreshold: 80,
            pollInterval: 10,
          },
          depth: 20,
        });

        watcher
          .on('add', (pathname) => this.add(mainWindow, pathname, collectionId, watchPath))
          .on('addDir', (pathname) => this.addDirectory(mainWindow, pathname, collectionId, watchPath))
          .on('change', (pathname) => this.change(mainWindow, pathname, collectionId, watchPath))
          .on('unlink', (pathname) => this.unlink(mainWindow, pathname, collectionId, watchPath))
          .on('unlinkDir', (pathname) => this.unlinkDir(mainWindow, pathname, collectionId, watchPath));

        this.watchers[watchPath] = watcher;
      }, 100);
    }
  }

  hasWatcher(watchPath) {
    return this.watchers[watchPath] != undefined ? true : false;
  }

  removeWatcher(watchPath) {
    if (this.watchers[watchPath]) {
      this.watchers[watchPath].close();
      this.watchers[watchPath] = null;
    }
  }
}

module.exports = Watcher;


================================================
FILE: packages/flowtest-electron/src/ipc/axiosClient.js
================================================
// lib/axiosClient.ts
const axios = require('axios');
const axiosRetry = require('axios-retry').default;

const axiosClient = (baseUrl, accessId, accessKey) => {
  const client = axios.create({
    baseURL: `${baseUrl}/api`,
    headers: {
      'Content-Type': 'application/json',
      'x-access-id': accessId,
      'x-access-key': accessKey,
    },
  });

  axiosRetry(client, {
    retries: 3, // Number of retries
    retryDelay: (retryCount) => {
      return retryCount * 1000; // Time interval between retries (1000 ms = 1 second)
    },
    retryCondition: (error) => {
      // Retry on network errors or rate limit errors or 5xx server errors
      return error.response?.status === 500 || error.response?.status === 429 || error.code === 'ECONNABORTED';
    },
  });

  return client;
};

module.exports = {
  axiosClient,
};


================================================
FILE: packages/flowtest-electron/src/ipc/collection.js
================================================
const fs = require('fs');
const path = require('path');
const axios = require('axios');
const { ipcMain, shell, dialog, app } = require('electron');
const SwaggerParser = require('@apidevtools/swagger-parser');
const JsonRefs = require('json-refs');
const createDirectory = require('../utils/filemanager/createdirectory');
const deleteDirectory = require('../utils/filemanager/deletedirectory');
const uuidv4 = require('uuid').v4;
const Collections = require('../store/collection');
const { parseOpenAPISpec } = require('../utils/collection');
const { isDirectory, pathExists } = require('../utils/filemanager/filesystem');
const createFile = require('../utils/filemanager/createfile');
const updateFile = require('../utils/filemanager/updatefile');
const deleteFile = require('../utils/filemanager/deletefile');
const readFile = require('../utils/filemanager/readfile');
const FlowtestAI = require('../ai/flowtestai');
const { stringify, parse } = require('flatted');
const { deserialize, serialize } = require('../utils/flowparser/parser');
const { axiosClient } = require('./axiosClient');
const FormData = require('form-data');
const { extend, cloneDeep } = require('lodash');

const collectionStore = new Collections();
const flowTestAI = new FlowtestAI();

const timeout = 60000;

const newAbortSignal = () => {
  const abortController = new AbortController();
  setTimeout(() => abortController.abort(), timeout || 0);

  return abortController.signal;
};

/** web platform: blob. */
const convertBase64ToBlob = async (base64) => {
  const response = await fetch(base64);
  const blob = await response.blob();
  return blob;
};

const registerRendererEventHandlers = (mainWindow, watcher) => {
  ipcMain.handle('renderer:open-directory-selection-dialog', async (event, arg) => {
    try {
      const result = await dialog.showOpenDialog(mainWindow, {
        properties: ['openDirectory'],
      });
      return result.filePaths[0];
    } catch (error) {
      return Promise.reject(error);
    }
  });

  ipcMain.handle('renderer:browser-window-ready', async (event) => {
    const savedCollections = collectionStore.getAll();

    for (let i = 0; i < savedCollections.length; i++) {
      if (isDirectory(savedCollections[i].pathname)) {
        mainWindow.webContents.send(
          'main:collection-created',
          savedCollections[i].id,
          path.basename(savedCollections[i].pathname),
          savedCollections[i].pathname,
          savedCollections[i].nodes,
        );

        watcher.addWatcher(mainWindow, savedCollections[i].pathname, savedCollections[i].id);
      } else {
        collectionStore.remove(savedCollections[i]);
      }
    }
  });

  ipcMain.handle('renderer:create-collection', async (event, openAPISpecFilePath, collectionFolderPath) => {
    try {
      const spec = fs.readFileSync(openAPISpecFilePath, 'utf8');
      // async/await syntax
      let api = await SwaggerParser.validate(openAPISpecFilePath);
      // console.log("API name: %s, Version: %s", api.info.title, api.info.version);

      // resolve references in openapi spec
      const resolvedSpec = await JsonRefs.resolveRefs(api);
      const parsedNodes = parseOpenAPISpec(resolvedSpec.resolved);

      const id = uuidv4();
      const collectionName = api.info.title;
      const pathname = path.join(collectionFolderPath, collectionName);

      const newCollection = {
        id: id,
        name: collectionName,
        pathname: pathname,
        openapi_spec: stringify(resolvedSpec.resolved),
        nodes: parsedNodes,
      };

      const result = createDirectory(newCollection.name, collectionFolderPath);
      console.log(`Created directory: ${result}`);
      createDirectory('environments', pathname);

      mainWindow.webContents.send('main:collection-created', id, path.basename(pathname), pathname, parsedNodes);

      watcher.addWatcher(mainWindow, pathname, id);
      collectionStore.add(newCollection);
    } catch (error) {
      return Promise.reject(error);
    }
  });

  ipcMain.handle('renderer:open-collection', async (event, openAPISpecFilePath, collectionFolderPath) => {
    try {
      if (isDirectory(collectionFolderPath)) {
        // async/await syntax
        const api = await SwaggerParser.validate(openAPISpecFilePath);
        // console.log("API name: %s, Version: %s", api.info.title, api.info.version);

        // resolve references in openapi spec
        const resolvedSpec = await JsonRefs.resolveRefs(api);
        const parsedNodes = parseOpenAPISpec(resolvedSpec.resolved);

        const id = uuidv4();
        const collectionName = api.info.title;

        const newCollection = {
          id: id,
          name: collectionName,
          pathname: collectionFolderPath,
          openapi_spec: stringify(resolvedSpec.resolved),
          nodes: parsedNodes,
        };

        mainWindow.webContents.send('main:collection-created', id, collectionName, collectionFolderPath, parsedNodes);

        watcher.addWatcher(mainWindow, collectionFolderPath, id);
        collectionStore.add(newCollection);
      } else {
        return Promise.reject(new Error(`Directory: ${collectionFolderPath} does not exist`));
      }
    } catch (error) {
      return Promise.reject(error);
    }
  });

  ipcMain.handle('renderer:delete-collection', async (event, collection) => {
    try {
      // deleteDirectory(collection.pathname);
      // console.log(`Deleted directory: ${collection.pathname}`);

      mainWindow.webContents.send('main:collection-deleted', collection.id);

      watcher.removeWatcher(collection.pathname);
      collectionStore.remove(collection);
    } catch (error) {
      return Promise.reject(error);
    }
  });

  ipcMain.handle('renderer:create-folder', async (event, name, path) => {
    try {
      const result = createDirectory(name, path);
      console.log(`Created directory: ${result}`);
    } catch (error) {
      return Promise.reject(error);
    }
  });

  ipcMain.handle('renderer:delete-folder', async (event, path) => {
    try {
      const result = deleteDirectory(path);
      console.log(`Deleted directory: ${path}`);
    } catch (error) {
      return Promise.reject(error);
    }
  });

  ipcMain.handle('renderer:create-environment', async (event, collectionPath, name) => {
    try {
      const envDir = path.join(collectionPath, 'environments');
      if (!isDirectory(envDir)) {
        createDirectory('environments', collectionPath);
      }
      const result = createFile(`${name}.env`, envDir, '');
      console.log(`Created file: ${name}.env`);
    } catch (error) {
      return Promise.reject(error);
    }
  });

  ipcMain.handle('renderer:update-environment', async (event, collectionPath, name, variables) => {
    try {
      const env = Object.entries(variables)
        .map(([key, value]) => `${key}: "${value}"`)
        .join('\n');
      const envDir = path.join(collectionPath, 'environments');
      updateFile(path.join(envDir, name), env);
    } catch (error) {
      return Promise.reject(error);
    }
  });

  ipcMain.handle('renderer:delete-environment', async (event, collectionPath, name) => {
    try {
      const envDir = path.join(collectionPath, 'environments');
      deleteFile(path.join(envDir, name));
      console.log(`Delete file: ${name}`);
    } catch (error) {
      return Promise.reject(error);
    }
  });

  ipcMain.handle('renderer:create-dotenv', async (event, collectionPath, content) => {
    try {
      createFile('.env', collectionPath, content || '');
    } catch (error) {
      return Promise.reject(error);
    }
  });

  ipcMain.handle('renderer:addOrUpdate-dotEnvironment', async (event, collectionPath, variables) => {
    try {
      const pathname = path.join(collectionPath, '.env');
      // variables should be of format `k1=v1\nk2=v2`;

      updateFile(pathname, variables);
    } catch (error) {
      return Promise.reject(error);
    }
  });

  ipcMain.handle('renderer:create-flowtest', async (event, name, path, flowData) => {
    try {
      if (isDirectory(path)) {
        const textData = deserialize(flowData);
        createFile(`${name}.flow`, path, JSON.stringify(textData, null, 4));
        console.log(`Created file: ${name}.flow`);
      }
    } catch (error) {
      return Promise.reject(error);
    }
  });

  ipcMain.handle('renderer:clone-flowtest', async (event, name, flowtestPath) => {
    try {
      const content = readFile(flowtestPath);
      createFile(`${name}.flow`, path.dirname(flowtestPath), content);
      console.log(`Cloned file: ${name}.flow`);
    } catch (error) {
      return Promise.reject(error);
    }
  });

  ipcMain.handle('renderer:read-flowtest', async (event, pathname, collectionId) => {
    try {
      const content = readFile(pathname);
      const flowData = serialize(JSON.parse(content));
      mainWindow.webContents.send('main:read-flowtest', pathname, collectionId, flowData);
    } catch (error) {
      return Promise.reject(error);
    }
  });

  ipcMain.handle('renderer:read-flowtest-sync', (event, pathname) => {
    const content = readFile(pathname);
    const flowData = serialize(JSON.parse(content));
    return flowData;
  });

  ipcMain.handle('renderer:update-flowtest', async (event, pathname, flowData) => {
    try {
      const textData = deserialize(flowData);
      updateFile(pathname, JSON.stringify(textData, null, 4));
      console.log(`Updated file: ${pathname}`);
    } catch (error) {
      return Promise.reject(error);
    }
  });

  ipcMain.handle('renderer:delete-flowtest', async (event, pathname) => {
    try {
      deleteFile(pathname);
      console.log(`Delete file: ${pathname}`);
    } catch (error) {
      return Promise.reject(error);
    }
  });

  ipcMain.handle('renderer:run-http-request', async (event, request, collectionPath) => {
    let requestSent;
    try {
      if (request.headers['content-type'] === 'multipart/form-data') {
        const formData = new FormData();
        const params = request.data;
        await params.map(async (param, index) => {
          if (param.type === 'text') {
            formData.append(param.key, param.value);
          }

          if (param.type === 'file') {
            let trimmedFilePath = param.value.trim();

            if (!path.isAbsolute(trimmedFilePath)) {
              trimmedFilePath = path.join(collectionPath, trimmedFilePath);
            }

            formData.append(param.key, fs.createReadStream(trimmedFilePath), path.basename(trimmedFilePath));
          }
        });

        request.data = formData;
        extend(request.headers, formData.getHeaders());
      }

      requestSent = {
        url: request.url,
        method: request.method,
        headers: request.headers,
        // form data obj gets serialized here so that it can be sent over wire
        // otherwise ipc communication errors out
        data: request.data ? JSON.parse(JSON.stringify(request.data)) : request.data,
      };

      const result = await axios({
        ...request,
        signal: newAbortSignal(),
      });

      return {
        request: requestSent,
        response: {
          status: result.status,
          statusText: result.statusText,
          data: result.data,
          headers: result.headers,
        },
      };
    } catch (error) {
      if (error?.response) {
        return {
          request: requestSent,
          response: {
            error: {
              status: error.response.status,
              statusText: error.response.statusText,
              data: error.response.data,
              headers: error.response.headers,
            },
          },
        };
      } else {
        return {
          request: requestSent,
          response: {
            error: {
              status: '',
              statusText: '',
              data: `An error occurred while running the request : ${error?.message}`,
            },
          },
        };
      }
    }
  });

  ipcMain.handle('renderer:generate-nodes-ai', async (event, instruction, collectionId, model) => {
    try {
      const collection = collectionStore.getAll().find((c) => c.id === collectionId);
      if (collection) {
        return await flowTestAI.generate(parse(collection.openapi_spec), instruction, model);
      } else {
        return Promise.reject(new Error('Collection not found'));
      }
    } catch (error) {
      return Promise.reject(error);
    }
  });

  ipcMain.handle('renderer:upload-logs', async (event, name, config, status, time, logs) => {
    function bytesToBase64(bytes) {
      const binString = Array.from(bytes, (byte) => String.fromCodePoint(byte)).join('');
      return btoa(binString);
    }

    try {
      const data = {
        scan_metadata: {
          version: 1,
          name,
          status,
          time,
        },
        scan: bytesToBase64(new TextEncoder().encode(JSON.stringify(logs))),
      };
      try {
        const response = await axiosClient(config.hostUrl, config.accessId, config.accessKey).post('/upload', data);
        return {
          upload: 'success',
          url: `${config.hostUrl}/scan/${response.data.data[0].id}`,
        };
      } catch (error) {
        if (error?.response) {
          if (error.response?.status >= 400 && error.response?.status < 500) {
            return {
              upload: 'fail',
              message: 'Unable to upload flow scan',
              reason: `${JSON.stringify(error.response?.data)}`,
            };
          }

          if (error.response?.status === 500) {
            return {
              upload: 'fail',
              message: 'Unable to upload flow scan',
              reason: 'Internal Server Error',
            };
          }
        }
        return {
          upload: 'fail',
          message: 'Unable to upload flow scan',
        };
      }
    } catch (error) {
      return Promise.reject(error);
    }
  });
};

module.exports = registerRendererEventHandlers;


================================================
FILE: packages/flowtest-electron/src/ipc/settings.js
================================================
const { ipcMain, shell, dialog, app } = require('electron');
const Settings = require('../store/settings');

const settingsStore = new Settings();

const registerSettingsEventHandlers = (mainWindow) => {
  ipcMain.handle('renderer:settings-window-ready', async (event) => {
    const savedSettings = settingsStore.getAll();

    mainWindow.webContents.send('main:saved-settings', savedSettings);
  });

  ipcMain.handle('renderer:add-logsyncconfig', async (event, config) => {
    try {
      settingsStore.addLogSyncConfig(config.enabled, config.hostUrl, config.accessId, config.accessKey);
      const savedSettings = settingsStore.getAll();

      mainWindow.webContents.send('main:saved-settings', savedSettings);
    } catch (error) {
      return Promise.reject(error);
    }
  });

  ipcMain.handle('renderer:add-genAIUsageDisclaimer', async (event, accepted) => {
    try {
      settingsStore.addGenAIUsageDisclaimer(accepted);
      const savedSettings = settingsStore.getAll();

      mainWindow.webContents.send('main:saved-settings', savedSettings);
    } catch (error) {
      return Promise.reject(error);
    }
  });
};

module.exports = registerSettingsEventHandlers;


================================================
FILE: packages/flowtest-electron/src/store/collection.js
================================================
const Store = require('electron-store');
const { isDirectory } = require('../utils/filemanager/filesystem');

class Collections {
  constructor() {
    this.store = new Store();
  }

  add(collection) {
    const collections = this.store.get('collections') || [];

    if (isDirectory(collection.pathname)) {
      if (!collections.find((c) => c.pathname === collection.pathname)) {
        collections.push(collection);
        this.store.set('collections', collections);
      }
    }
  }

  remove(collection) {
    const collections = this.store.get('collections') || [];

    if (collections.find((c) => c.id === collection.id)) {
      this.store.set(
        'collections',
        collections.filter((c) => c.pathname !== collection.pathname),
      );
    }
  }

  getAll() {
    return this.store.get('collections') || [];
  }

  removeAll() {
    return this.store.set('collections', []);
  }
}

module.exports = Collections;


================================================
FILE: packages/flowtest-electron/src/store/settings.js
================================================
const Store = require('electron-store');

class Settings {
  constructor() {
    this.store = new Store();
  }

  addLogSyncConfig(enabled, hostUrl, accessId, accessKey) {
    this.store.set('logSyncConfig', { enabled, hostUrl, accessId, accessKey });
  }

  addGenAIUsageDisclaimer(accepted) {
    this.store.set('genAIUsageDisclaimer', accepted);
  }

  getAll() {
    return {
      logSyncConfig: this.store.get('logSyncConfig') || {},
      genAIUsageDisclaimer: this.store.get('genAIUsageDisclaimer') || false,
    };
  }

  clearAll() {
    this.store.set('logSyncConfig', {});
    this.store.set('genAIUsageDisclaimer', false);
  }
}

module.exports = Settings;


================================================
FILE: packages/flowtest-electron/src/utils/collection.js
================================================
const { generateRequestBodyExample } = require('./generate-request-body');
const {
  generateQueryParamsExample,
  generateParameterExample,
  generatePathParamsExample,
} = require('./generate-request-parameters');

const computeUrl = (baseUrl, path) => {
  if (baseUrl.charAt(baseUrl.length - 1) === '/' && path.charAt(0) === '/') {
    return baseUrl + path.substring(1, path.length);
  } else if (baseUrl.charAt(baseUrl.length - 1) !== '/' && path.charAt(0) !== '/') {
    return baseUrl + '/' + path;
  } else {
    return baseUrl + path;
  }
};

const replaceSingleToDoubleCurlyBraces = (str) => {
  // Replace opening curly braces
  str = str.replace(/{/g, '{{');
  // Replace closing curly braces
  str = str.replace(/}/g, '}}');
  return str;
};

const parseOpenAPISpec = (collection) => {
  let parsedNodes = [];
  try {
    // servers is array,, figure case where there can be multiple servers
    const baseUrl = collection['servers'].length > 1 ? '{baseUrl}' : collection['servers'][0]['url'];
    Object.entries(collection['paths']).map(([path, operations], _) => {
      const commonParameters = Object.prototype.hasOwnProperty.call(operations, 'parameters')
        ? operations['parameters']
        : [];
      const { parameters, ...operationsFiltered } = operations;
      Object.entries(operationsFiltered).map(([requestType, request], _) => {
        const summary = request['summary'];
        const operationId = request['operationId'];
        const tags = request['tags'];
        var url = replaceSingleToDoubleCurlyBraces(computeUrl(baseUrl, path));
        var variables = {};
        var requestBody = {};
        const pathParameters = [];
        const queryParameters = [];

        const requestParameters = commonParameters.map((obj) => {
          if (request['parameters']) {
            // Find the object in the second array that has the same id as the current object
            const objFromArr2 = request['parameters'].find((o) => o.name === obj.name && o.in === obj.in);
            // If found, merge the two objects, otherwise return the original object
            return objFromArr2 ? { ...obj, ...objFromArr2 } : obj;
          } else {
            return obj;
          }
        });

        if (request['parameters']) {
          // Add any objects from the second array that do not exist in the first array
          request['parameters'].forEach((obj) => {
            if (!commonParameters.some((o) => o.name === obj.name && o.in === obj.in)) {
              requestParameters.push(obj);
            }
          });
        }

        if (requestParameters.length > 0) {
          let firstQueryParam = true;
          requestParameters.map((value, _) => {
            if (value['in'] === 'query') {
              if (firstQueryParam) {
                url = url.concat(`?${value['name']}={{${value['name']}}}`);
                firstQueryParam = false;
              } else {
                url = url.concat(`&${value['name']}={{${value['name']}}}`);
              }
              queryParameters.push(value);
            }

            if (value['in'] === 'path') {
              pathParameters.push(value);
            }
          });
        }

        if (queryParameters.length > 0) {
          const res = generateQueryParamsExample(queryParameters);
          Array.from(res.entries()).map(([key, value], index) => {
            variables[key] = {
              type: typeof value,
              value,
            };
          });
        }

        if (pathParameters.length > 0) {
          const res = generatePathParamsExample(pathParameters);
          Array.from(res.entries()).map(([key, value], index) => {
            variables[key] = {
              type: typeof value,
              value,
            };
          });
        }

        if (request['requestBody']) {
          if (request['requestBody']['content']['application/json']) {
            requestBody = {
              type: 'raw-json',
              body: JSON.stringify(
                generateRequestBodyExample(request['requestBody']['content']['application/json']['schema']),
              ),
            };
          }

          if (request['requestBody']['content']['multipart/form-data']) {
            requestBody = {
              type: 'form-data',
              body: [],
            };
          }
        }

        const finalNode = {
          url: url,
          description: summary,
          operationId: operationId,
          requestType: requestType.toUpperCase(),
          tags: tags,
          requestBody,
          preReqVars: variables,
        };

        parsedNodes.push(finalNode);
      });
    });
  } catch (err) {
    console.error(err);
  }
  return parsedNodes;
};

module.exports = {
  parseOpenAPISpec,
};


================================================
FILE: packages/flowtest-electron/src/utils/collection.test.js
================================================
const { generateRequestBodyExample } = require('./generate-request-body.js');
const { generatePathParamsExample, generateQueryParamsExample } = require('./generate-request-parameters.js');

describe('collection parser', () => {
  it('should generate request body example', () => {
    console.log(JSON.stringify(generateRequestBodyExample(userSchema), null, 2));
    console.log(JSON.stringify(generateRequestBodyExample(productSchema), null, 2));
    console.log(JSON.stringify(generateRequestBodyExample(complexSchema), null, 2));
  });

  it('should generate request parameters example', () => {
    console.log('Path Parameters Example:', generatePathParamsExample(pathParameters).toString());
    console.log('Query Parameters Example:', generateQueryParamsExample(queryParameters).toString());
  });
});

const userSchema = {
  type: 'object',
  properties: {
    id: {
      type: 'integer',
      format: 'int64',
      example: 1,
      minimum: 1,
    },
    name: {
      type: 'string',
      example: 'John Doe',
      minLength: 3,
      maxLength: 20,
    },
    email: {
      type: 'string',
      format: 'email',
      example: 'john.doe@example.com',
    },
    birthdate: {
      type: 'string',
      format: 'date',
      example: '1990-01-01',
    },
    website: {
      type: 'string',
      format: 'uri',
      example: 'https://johndoe.com',
    },
    role: {
      type: 'string',
      enum: ['admin', 'user', 'guest'],
      example: 'user',
    },
    username: {
      type: 'string',
      pattern: '^[a-zA-Z0-9]{3,}$',
      example: 'user123',
    },
    interests: {
      type: 'array',
      items: {
        type: 'string',
      },
      example: ['coding', 'reading'],
    },
  },
};

const productSchema = {
  type: 'object',
  properties: {
    id: {
      type: 'integer',
      format: 'int32',
      example: 101,
      minimum: 1,
      maximum: 1000,
    },
    name: {
      type: 'string',
      example: 'Sample Product',
      minLength: 3,
    },
    price: {
      type: 'number',
      format: 'double',
      example: 19.99,
      minimum: 0,
      maximum: 1000,
    },
    tags: {
      type: 'array',
      items: {
        type: 'string',
        example: 'tag1',
      },
    },
    status: {
      type: 'string',
      enum: ['available', 'out of stock', 'discontinued'],
      example: 'available',
    },
    releaseDate: {
      type: 'string',
      format: 'date-time',
      example: '2023-01-01T00:00:00Z',
    },
  },
};

const complexSchema = {
  type: 'object',
  properties: {
    name: {
      type: 'string',
      example: 'Complex Example',
    },
    detail: {
      oneOf: [
        { type: 'string', example: 'OneOf String' },
        { type: 'integer', example: 42 },
      ],
    },
    options: {
      anyOf: [
        { type: 'boolean', example: true },
        { type: 'string', example: 'AnyOf String' },
      ],
    },
    allDetails: {
      allOf: [
        {
          type: 'object',
          properties: {
            part1: {
              type: 'string',
              example: 'Part 1',
            },
          },
        },
        {
          type: 'object',
          properties: {
            part2: {
              type: 'number',
              example: 123.45,
            },
          },
        },
      ],
    },
  },
};

// Example usage:

const pathParameters = [
  {
    name: 'userId',
    in: 'path',
    required: true,
    schema: {
      type: 'integer',
      format: 'int64',
      example: 123,
      minimum: 1,
    },
  },
  {
    name: 'username',
    in: 'path',
    required: true,
    schema: {
      type: 'string',
      minLength: 3,
      maxLength: 20,
      example: 'john_doe',
    },
  },
];

const queryParameters = [
  {
    name: 'page',
    in: 'query',
    schema: {
      type: 'integer',
      example: 1,
      minimum: 1,
    },
  },
  {
    name: 'limit',
    in: 'query',
    schema: {
      type: 'integer',
      example: 10,
      minimum: 1,
      maximum: 100,
    },
  },
  {
    name: 'sort',
    in: 'query',
    schema: {
      type: 'string',
      enum: ['asc', 'desc'],
      example: 'asc',
    },
  },
  {
    name: 'filter',
    in: 'query',
    schema: {
      type: 'array',
      items: {
        type: 'string',
        example: 'status:active',
      },
    },
  },
  {
    name: 'search',
    in: 'query',
    schema: {
      type: 'string',
      example: 'example',
    },
  },
];


================================================
FILE: packages/flowtest-electron/src/utils/filemanager/createdirectory.js
================================================
const fs = require('fs');
const { isDirectory, pathExists } = require('./filesystem');
const path = require('path');

const createDirectory = (name, basePath) => {
  // now validate the name and path
  if (!name) {
    throw new Error('Directory name is required');
  }

  if (!basePath) {
    throw new Error('Directory path is required');
  }

  // check if the directory exists
  if (!isDirectory(basePath)) {
    throw new Error('Path is not a directory');
  }

  // check if the directory already exists
  const directoryPath = path.join(basePath, name);

  if (isDirectory(directoryPath)) {
    throw new Error('The directory already exists');
  }

  // now create the directory
  return fs.mkdirSync(directoryPath, {
    mode: 0o777,
    recursive: true,
  });
};

module.exports = createDirectory;


================================================
FILE: packages/flowtest-electron/src/utils/filemanager/createfile.js
================================================
const fs = require('fs');
const dpath = require('path');
const { isDirectory, pathExists } = require('./filesystem');

const createFile = (name, path, content) => {
  // now validate the name and path
  if (!name) {
    throw new Error('File name is required');
  }

  if (!path) {
    throw new Error('Directory path is required');
  }

  // check if the directory exists
  if (!isDirectory(path)) {
    throw new Error('Path is not a directory');
  }

  // check if the file already exists
  const filePath = dpath.join(path, name);

  if (pathExists(filePath)) {
    throw new Error(`File already exists ${filePath}`);
  }

  // now create the file
  return fs.writeFileSync(filePath, String(content), 'utf8');
};

module.exports = createFile;


================================================
FILE: packages/flowtest-electron/src/utils/filemanager/deletedirectory.js
================================================
const fs = require('fs');
const { isDirectory } = require('./filesystem');

const deleteDirectory = (path) => {
  if (!path) {
    throw new Error('Directory path is required');
  }

  // check if the directory exists
  if (!isDirectory(path)) {
    throw new Error('Path is not a directory');
  }

  // now delete the directory
  return fs.rmSync(path, { recursive: true, force: true });
};

module.exports = deleteDirectory;


================================================
FILE: packages/flowtest-electron/src/utils/filemanager/deletefile.js
================================================
const fs = require('fs');
const { pathExists } = require('./filesystem');

const deleteFile = (path) => {
  if (!path) {
    throw new Error('File path is required');
  }

  // check if file exists
  if (!pathExists(path)) {
    throw new Error('File does not exist');
  }

  // now delete the file
  return fs.rmSync(path, { recursive: true, force: true });
};

module.exports = deleteFile;


================================================
FILE: packages/flowtest-electron/src/utils/filemanager/filesystem.js
================================================
const fs = require('fs');
const path = require('path');

/**
 * Determine if the given path is directory
 */
const isDirectory = (path) => {
  try {
    return fs.existsSync(path) && fs.lstatSync(path).isDirectory();
  } catch (e) {
    // lstatSync throws an error if path doesn't exist
    return false;
  }
};

/**
 * Determine if the given path exists
 */
const pathExists = (path) => {
  try {
    fs.accessSync(path);
    return true;
  } catch (error) {
    return false;
  }
};

const getSubdirectoriesFromRoot = (rootPath, pathname) => {
  // convert to unix style path
  pathname = slash(pathname);
  rootPath = slash(rootPath);

  const relativePath = path.relative(rootPath, pathname);
  return relativePath ? relativePath.split(path.sep) : [];
};

const getDirectoryName = (pathname) => {
  // convert to unix style path
  pathname = slash(pathname);

  return path.dirname(pathname);
};

const isWindowsOS = () => {
  return process.platform === 'win32';
};

const isMacOS = () => {
  return process.platform === 'darwin';
};

const PATH_SEPARATOR = isWindowsOS() ? '\\' : '/';

const slash = (path) => {
  const isExtendedLengthPath = /^\\\\\?\\/.test(path);

  if (isExtendedLengthPath) {
    return path;
  }

  return path.replace(/\\/g, '/');
};

module.exports = {
  isDirectory,
  pathExists,
  getSubdirectoriesFromRoot,
  getDirectoryName,
  isMacOS,
  PATH_SEPARATOR,
};


================================================
FILE: packages/flowtest-electron/src/utils/filemanager/readfile.js
================================================
const fs = require('fs');
const { pathExists } = require('./filesystem');

const readFile = (path) => {
  if (!path) {
    throw new Error('File path is required');
  }

  // check if file exists
  if (!pathExists(path)) {
    throw new Error('File does not exist');
  }

  // now delete the file
  return fs.readFileSync(path, 'utf8');
};

module.exports = readFile;


================================================
FILE: packages/flowtest-electron/src/utils/filemanager/updatefile.js
================================================
const fs = require('fs');
const { pathExists } = require('./filesystem');

const upadateFile = (path, content) => {
  if (!path) {
    throw new Error('File path is required');
  }

  // check if file exists
  if (!pathExists(path)) {
    throw new Error('File does not exist');
  }

  // now update the file
  return fs.writeFileSync(path, String(content), 'utf8');
};

module.exports = upadateFile;


================================================
FILE: packages/flowtest-electron/src/utils/flowparser/AssertNode.js
================================================
const { Node } = require('./Node');

class AssertNode extends Node {
  constructor() {
    super('assertNode');
  }

  serialize(id, data, metadata) {
    return {
      id,
      type: this.type,
      data,
      ...metadata,
    };
  }

  deserialize(node) {
    const id = node.id;
    const data = node.data;
    delete node.id;
    delete node.data;
    const metadata = node;

    return {
      id,
      data,
      metadata,
    };
  }
}

module.exports = {
  AssertNode,
};


================================================
FILE: packages/flowtest-electron/src/utils/flowparser/AuthNode.js
================================================
const { Node } = require('./Node');

class AuthNode extends Node {
  constructor() {
    super('authNode');
  }

  serialize(id, data, metadata) {
    return {
      id,
      type: this.type,
      data,
      ...metadata,
    };
  }

  deserialize(node) {
    const id = node.id;
    const data = node.data;
    delete node.id;
    delete node.data;
    const metadata = node;

    return {
      id,
      data,
      metadata,
    };
  }
}

module.exports = {
  AuthNode,
};


================================================
FILE: packages/flowtest-electron/src/utils/flowparser/DelayNode.js
================================================
const { Node } = require('./Node');

class DelayNode extends Node {
  constructor() {
    super('delayNode');
  }

  serialize(id, data, metadata) {
    return {
      id,
      type: this.type,
      data,
      ...metadata,
    };
  }

  deserialize(node) {
    const id = node.id;
    const data = node.data;
    delete node.id;
    delete node.data;
    const metadata = node;

    return {
      id,
      data,
      metadata,
    };
  }
}

module.exports = {
  DelayNode,
};


================================================
FILE: packages/flowtest-electron/src/utils/flowparser/NestedFlowNode.js
================================================
const { Node } = require('./Node');

class NestedFlowNode extends Node {
  constructor() {
    super('flowNode');
  }

  serialize(id, data, metadata) {
    return {
      id,
      type: this.type,
      data,
      ...metadata,
    };
  }

  deserialize(node) {
    const id = node.id;
    const data = node.data;
    delete node.id;
    delete node.data;
    const metadata = node;

    return {
      id,
      data,
      metadata,
    };
  }
}

module.exports = {
  NestedFlowNode,
};


================================================
FILE: packages/flowtest-electron/src/utils/flowparser/Node.js
================================================
class Node {
  constructor(type) {
    this.type = type;
  }

  serialize(id, data, metadata) {
    throw new Error('Serialize method must be implemented by subclasses');
  }

  deserialize(node) {
    throw new Error('Deserialize method must be implemented by subclasses');
  }
}

module.exports = {
  Node,
};


================================================
FILE: packages/flowtest-electron/src/utils/flowparser/OutputNode.js
================================================
const { Node } = require('./Node');

class OutputNode extends Node {
  constructor() {
    super('outputNode');
  }

  serialize(id, data, metadata) {
    return {
      id,
      type: this.type,
      data,
      ...metadata,
    };
  }

  deserialize(node) {
    const id = node.id;
    const { ['output']: _, ...data } = node.data;
    delete node.id;
    delete node.data;
    const metadata = node;

    return {
      id,
      data,
      metadata,
    };
  }
}

module.exports = {
  OutputNode,
};


================================================
FILE: packages/flowtest-electron/src/utils/flowparser/RequestNode.js
================================================
const { Node } = require('./Node');

class RequestNode extends Node {
  constructor() {
    super('requestNode');
  }

  serialize(id, data, metadata) {
    return {
      id,
      type: this.type,
      data,
      ...metadata,
    };
  }

  deserialize(node) {
    const id = node.id;
    const data = node.data;
    delete node.id;
    delete node.data;
    const metadata = node;

    return {
      id,
      data,
      metadata,
    };
  }
}

module.exports = {
  RequestNode,
};


================================================
FILE: packages/flowtest-electron/src/utils/flowparser/SetVarNode.js
================================================
const { Node } = require('./Node');

class SetVarNode extends Node {
  constructor() {
    super('setVarNode');
  }

  serialize(id, data, metadata) {
    return {
      id,
      type: this.type,
      data,
      ...metadata,
    };
  }

  deserialize(node) {
    const id = node.id;
    const data = node.data;
    delete node.id;
    delete node.data;
    const metadata = node;

    return {
      id,
      data,
      metadata,
    };
  }
}

module.exports = {
  SetVarNode,
};


================================================
FILE: packages/flowtest-electron/src/utils/flowparser/StartNode.js
================================================
const { Node } = require('./Node');

class StartNode extends Node {
  constructor() {
    super('startNode');
  }

  serialize(id, data, metadata) {
    return {
      id,
      ...metadata,
    };
  }

  deserialize(node) {
    const id = node.id;
    delete node.id;
    const metadata = node;

    return {
      id,
      metadata,
    };
  }
}

module.exports = {
  StartNode,
};


================================================
FILE: packages/flowtest-electron/src/utils/flowparser/parser.js
================================================
const { cloneDeep } = require('lodash');
const { AuthNode } = require('./AuthNode');
const { NestedFlowNode } = require('./NestedFlowNode');
const { DelayNode } = require('./DelayNode');
const { AssertNode } = require('./AssertNode');
const { OutputNode } = require('./OutputNode');
const { RequestNode } = require('./RequestNode');
const { StartNode } = require('./StartNode');
const { SetVarNode } = require('./SetVarNode');

const VERSION = 1;

const deserialize = (flowData) => {
  // we don't want to modify original object
  const flowDataCopy = cloneDeep(flowData);

  const textData = {};
  textData.version = VERSION;
  textData.graph = {};

  if (flowData) {
    if (flowData.nodes) {
      const nodes = flowDataCopy.nodes;
      textData.graph.data = {};
      textData.graph.data.nodes = {};
      textData.graph.metadata = {};
      textData.graph.metadata.nodes = {};

      nodes.forEach((node) => {
        if (node.type === 'startNode') {
          const sNode = new StartNode();
          const result = sNode.deserialize(node);

          textData.graph.data.nodes[result.id] = {
            type: 'startNode',
          };

          textData.graph.metadata.nodes[result.id] = {
            type: 'startNode',
            ...result.metadata,
          };
        }

        if (node.type === 'authNode') {
          const aNode = new AuthNode();
          const result = aNode.deserialize(node);
          textData.graph.data.nodes[result.id] = {
            type: 'authNode',
            auth: result.data,
          };

          textData.graph.metadata.nodes[result.id] = {
            type: 'authNode',
            ...result.metadata,
          };
        }

        if (node.type === 'requestNode') {
          const rNode = new RequestNode();
          const result = rNode.deserialize(node);
          textData.graph.data.nodes[result.id] = {
            type: 'requestNode',
            ...result.data,
          };

          textData.graph.metadata.nodes[result.id] = {
            type: 'requestNode',
            ...result.metadata,
          };
        }

        if (node.type === 'outputNode') {
          const oNode = new OutputNode();
          const result = oNode.deserialize(node);
          textData.graph.data.nodes[result.id] = {
            type: 'outputNode',
            ...result.data,
          };

          textData.graph.metadata.nodes[result.id] = {
            type: 'outputNode',
            ...result.metadata,
          };
        }

        if (node.type === 'delayNode') {
          const dNode = new DelayNode();
          const result = dNode.deserialize(node);
          textData.graph.data.nodes[result.id] = {
            type: 'delayNode',
            ...result.data,
          };

          textData.graph.metadata.nodes[result.id] = {
            type: 'delayNode',
            ...result.metadata,
          };
        }

        if (node.type === 'assertNode') {
          const eNode = new AssertNode();
          const result = eNode.deserialize(node);
          textData.graph.data.nodes[result.id] = {
            type: 'assertNode',
            ...result.data,
          };

          textData.graph.metadata.nodes[result.id] = {
            type: 'assertNode',
            ...result.metadata,
          };
        }

        if (node.type === 'flowNode') {
          const fNode = new NestedFlowNode();
          const result = fNode.deserialize(node);
          textData.graph.data.nodes[result.id] = {
            type: 'flowNode',
            ...result.data,
          };

          textData.graph.metadata.nodes[result.id] = {
            type: 'flowNode',
            ...result.metadata,
          };
        }

        if (node.type === 'setVarNode') {
          const sNode = new SetVarNode();
          const result = sNode.deserialize(node);
          textData.graph.data.nodes[result.id] = {
            type: 'setVarNode',
            ...result.data,
          };

          textData.graph.metadata.nodes[result.id] = {
            type: 'setVarNode',
            ...result.metadata,
          };
        }
      });
    }

    if (flowData.edges) {
      const edges = flowDataCopy.edges;
      textData.graph.data.edges = [];
      textData.graph.metadata.edges = {};

      edges.forEach((edge) => {
        textData.graph.data.edges.push(`${edge.source} -> ${edge.target}`);

        const { ['id']: _, ..._edge } = edge;
        textData.graph.metadata.edges[edge.id] = _edge;
      });
    }

    if (flowData.viewport) {
      textData.graph.metadata.viewport = flowDataCopy.viewport;
    }
  }

  return textData;
};

const serialize = (textData) => {
  const flowData = {};
  flowData.nodes = [];
  flowData.edges = [];
  flowData.viewport = { x: 0, y: 0, zoom: 1 };

  // we don't want to modify original object
  const textDataCopy = cloneDeep(textData);
  const version = textDataCopy.version;
  if (version === 1) {
    if (textDataCopy.graph.data) {
      Object.entries(textDataCopy.graph.data.nodes).map(([key, value], index) => {
        const id = key;

        if (value.type === 'startNode') {
          const metadata = textDataCopy.graph.metadata.nodes[id];
          const sNode = new StartNode();
          const result = sNode.serialize(id, undefined, metadata);

          flowData.nodes.push(result);
        }

        if (value.type === 'authNode') {
          const data = value.auth;
          const metadata = textDataCopy.graph.metadata.nodes[id];
          const aNode = new AuthNode();
          const result = aNode.serialize(id, data, metadata);
          flowData.nodes.push(result);
        }

        if (value.type === 'requestNode') {
          const data = value;
          const metadata = textDataCopy.graph.metadata.nodes[id];
          const rNode = new RequestNode();
          const result = rNode.serialize(id, data, metadata);
          flowData.nodes.push(result);
        }

        if (value.type === 'outputNode') {
          const data = value;
          const metadata = textDataCopy.graph.metadata.nodes[id];
          const oNode = new OutputNode();
          const result = oNode.serialize(id, data, metadata);
          flowData.nodes.push(result);
        }

        if (value.type === 'delayNode') {
          const data = value;
          const metadata = textDataCopy.graph.metadata.nodes[id];
          const dNode = new DelayNode();
          const result = dNode.serialize(id, data, metadata);
          flowData.nodes.push(result);
        }

        if (value.type === 'assertNode') {
          const data = value;
          const metadata = textDataCopy.graph.metadata.nodes[id];
          const dNode = new AssertNode();
          const result = dNode.serialize(id, data, metadata);
          flowData.nodes.push(result);
        }

        if (value.type === 'flowNode') {
          const data = value;
          const metadata = textDataCopy.graph.metadata.nodes[id];
          const fNode = new NestedFlowNode();
          const result = fNode.serialize(id, data, metadata);
          flowData.nodes.push(result);
        }

        if (value.type === 'setVarNode') {
          const data = value;
          const metadata = textDataCopy.graph.metadata.nodes[id];
          const cNode = new SetVarNode();
          const result = cNode.serialize(id, data, metadata);
          flowData.nodes.push(result);
        }
      });

      Object.entries(textDataCopy.graph.metadata.edges).map(([key, value], index) => {
        flowData.edges.push({
          id: key,
          ...value,
        });
      });

      if (textDataCopy.graph.metadata.viewport) {
        flowData.viewport = textDataCopy.graph.metadata.viewport;
      }
    }
  } else {
    throw new Error('Version not recognized');
  }

  return flowData;
};

module.exports = {
  deserialize,
  serialize,
};


================================================
FILE: packages/flowtest-electron/src/utils/generate-request-body.js
================================================
const generateRequestBodyExample = (schema, level = 0, context = { processedSchemas: new Set() }) => {
  if (!schema) return {};

  if (schema.example !== undefined) {
    return schema.example;
  }

  if (schema.enum) {
    return schema.example || schema.enum[0];
  }

  if (schema.oneOf) {
    return generateRequestBodyExample(schema.oneOf[0], level + 1, context);
  }

  if (schema.anyOf) {
    return generateRequestBodyExample(schema.anyOf[0], level + 1, context);
  }

  if (schema.allOf) {
    return generateAllOfExample(schema.allOf, level + 1, context);
  }

  switch (schema.type) {
    case 'object':
      return generateObjectExample(schema, level + 1, context);
    case 'array':
      return generateArrayExample(schema, level + 1, context);
    case 'string':
      return generateStringExample(schema);
    case 'integer':
      return generateIntegerExample(schema);
    case 'number':
      return generateNumberExample(schema);
    case 'boolean':
      return schema.example || true;
    default:
      return schema.example || null;
  }
};

const generateAllOfExample = (schemas, level, context) => {
  const example = {};
  schemas.forEach((subSchema) => {
    const subExample = generateRequestBodyExample(subSchema, level, context);
    Object.assign(example, subExample);
  });
  return example;
};

const generateObjectExample = (schema, level, context) => {
  if (schema.example !== undefined) {
    return schema.example;
  }

  if (context.processedSchemas.has(schema) && level > 1) {
    return {};
  }
  context.processedSchemas.add(schema);

  const example = {};
  const properties = schema.properties || {};

  for (const [key, propertySchema] of Object.entries(properties)) {
    example[key] = generateRequestBodyExample(propertySchema, level, context);
  }

  if (schema.additionalProperties) {
    example.additionalProperty1 = generateRequestBodyExample(schema.additionalProperties, level, context);
    example.additionalProperty2 = generateRequestBodyExample(schema.additionalProperties, level, context);
  }

  context.processedSchemas.delete(schema);
  return example;
};

const generateArrayExample = (schema, level, context) => {
  if (schema.example !== undefined) {
    return schema.example;
  }
  const itemsSchema = schema.items || {};
  return [generateRequestBodyExample(itemsSchema, level, context)];
};

const generateStringExample = (schema) => {
  let example = String(schema.example || 'string');

  if (schema.minLength || schema.maxLength) {
    example = generateStringWithLengthConstraints(example, schema.minLength, schema.maxLength);
  }

  switch (schema.format) {
    case 'date-time':
      return schema.example || new Date().toISOString();
    case 'date':
      return schema.example || new Date().toISOString().split('T')[0];
    case 'email':
      return schema.example || 'example@example.com';
    case 'uuid':
      return schema.example || '123e4567-e89b-12d3-a456-426614174000';
    case 'uri':
      return schema.example || 'https://example.com';
    case 'hostname':
      return schema.example || 'example.com';
    case 'ipv4':
      return schema.example || '192.168.0.1';
    case 'ipv6':
      return schema.example || '2001:0db8:85a3:0000:0000:8a2e:0370:7334';
    case 'byte':
      return schema.example || btoa('example');
    case 'binary':
      return schema.example || 'binary data';
    case 'password':
      return schema.example || 'password';
    default:
      return example;
  }
};

const generateStringWithLengthConstraints = (str, minLength, maxLength) => {
  if (minLength) {
    while (str.length < minLength) {
      str += 'a';
    }
  }
  if (maxLength) {
    str = str.substring(0, maxLength);
  }
  return str;
};

const generateIntegerExample = (schema) => {
  const min = schema.minimum || 0;
  const max = schema.maximum || min + 100;
  return schema.example || Math.floor(Math.random() * (max - min + 1)) + min;
};

const generateNumberExample = (schema) => {
  const min = schema.minimum || 0.0;
  const max = schema.maximum || min + 100.0;
  return schema.example || Math.random() * (max - min) + min;
};

module.exports = {
  generateRequestBodyExample,
};


================================================
FILE: packages/flowtest-electron/src/utils/generate-request-parameters.js
================================================
function generateParameterExample(parameter) {
  if (!parameter.schema) return {};

  const schema = parameter.schema;

  if (schema.enum) {
    return schema.example || schema.enum[0];
  }

  switch (schema.type) {
    case 'string':
      return generateStringExample(schema);
    case 'integer':
      return generateIntegerExample(schema);
    case 'number':
      return generateNumberExample(schema);
    case 'boolean':
      return schema.example || true;
    case 'array':
      return generateArrayExample(schema);
    default:
      return schema.example || null;
  }
}

function generateStringExample(schema) {
  let example = schema.example || 'string';

  if (schema.minLength || schema.maxLength) {
    example = generateStringWithLengthConstraints(example, schema.minLength, schema.maxLength);
  }

  switch (schema.format) {
    case 'date-time':
      return schema.example || new Date().toISOString();
    case 'date':
      return schema.example || new Date().toISOString().split('T')[0];
    case 'email':
      return schema.example || 'example@example.com';
    case 'uuid':
      return schema.example || '123e4567-e89b-12d3-a456-426614174000';
    case 'uri':
      return schema.example || 'https://example.com';
    case 'hostname':
      return schema.example || 'example.com';
    case 'ipv4':
      return schema.example || '192.168.0.1';
    case 'ipv6':
      return schema.example || '2001:0db8:85a3:0000:0000:8a2e:0370:7334';
    case 'byte':
      return schema.example || btoa('example');
    case 'binary':
      return schema.example || 'binary data';
    case 'password':
      return schema.example || 'password';
    default:
      return example;
  }
}

function generateStringWithLengthConstraints(str, minLength, maxLength) {
  if (minLength) {
    while (str.length < minLength) {
      str += 'a';
    }
  }
  if (maxLength) {
    str = str.substring(0, maxLength);
  }
  return str;
}

function generateIntegerExample(schema) {
  const min = schema.minimum || 0;
  const max = schema.maximum || min + 100;
  return schema.example || Math.floor(Math.random() * (max - min + 1)) + min;
}

function generateNumberExample(schema) {
  const min = schema.minimum || 0.0;
  const max = schema.maximum || min + 100.0;
  return schema.example || Math.random() * (max - min) + min;
}

function generateArrayExample(schema) {
  const itemsSchema = schema.items || {};
  return [generateParameterExample({ schema: itemsSchema })];
}

const generatePathParamsExample = (parameters) => {
  const examples = {};
  parameters.forEach((param) => {
    examples[param.name] = generateParameterExample(param);
  });
  return new URLSearchParams(examples);
};

const generateQueryParamsExample = (parameters) => {
  const examples = {};
  parameters.forEach((param) => {
    examples[param.name] = generateParameterExample(param);
  });
  return new URLSearchParams(examples);
};

module.exports = {
  generateParameterExample,
  generatePathParamsExample,
  generateQueryParamsExample,
};


================================================
FILE: packages/flowtest-electron/tests/store/collection-store.test.js
================================================
const path = require('path');

const Collections = require('../../src/store/collection');
const createDirectory = require('../../src/utils/filemanager/createdirectory');
const deleteDirectory = require('../../src/utils/filemanager/deletedirectory');

describe('collection-store', () => {
  it('should create and remove collection', async () => {
    const store = new Collections();
    const newCollection = {
      id: '1234',
      name: 'test',
      pathname: `${__dirname}/test`,
      openapi_spec: '',
      nodes: '{}',
    };

    const newestCollection = {
      id: '12345',
      name: 'test1',
      pathname: `${__dirname}/test1`,
      openapi_spec: '',
      nodes: '{}',
    };

    store.removeAll();
    expect(store.getAll()).toEqual([]);

    // adding a collection whose directory doesn't exist
    store.add(newCollection);
    expect(store.getAll()).toEqual([]);

    createDirectory('test', __dirname);
    store.add(newCollection);
    expect(store.getAll()).toEqual([newCollection]);

    createDirectory('test1', __dirname);
    store.add(newestCollection);
    expect(store.getAll()).toEqual([newCollection, newestCollection]);

    store.remove(newCollection);
    expect(store.getAll()).toEqual([newestCollection]);

    store.remove(newestCollection);
    expect(store.getAll()).toEqual([]);

    deleteDirectory(`${__dirname}/test`);
    deleteDirectory(`${__dirname}/test1`);
  });

  it('collection set should be unique by pathname', async () => {
    const store = new Collections();
    const newCollection = {
      id: '1234',
      name: 'test',
      pathname: `${__dirname}/test`,
      openapi_spec: '',
      nodes: '{}',
    };

    const newestCollection = {
      id: '12345',
      name: 'test',
      pathname: `${__dirname}/test`,
      openapi_spec: '',
      nodes: '{}',
    };

    store.removeAll();
    expect(store.getAll()).toEqual([]);

    createDirectory('test', __dirname);
    store.add(newCollection);
    expect(store.getAll()).toEqual([newCollection]);

    // collection in the store should be unique by path
    store.add(newestCollection);
    expect(store.getAll()).toEqual([newCollection]);

    store.remove(newCollection);
    expect(store.getAll()).toEqual([]);

    deleteDirectory(`${__dirname}/test`);
  });
});


================================================
FILE: packages/flowtest-electron/tests/store/settings-store.test.js
================================================
const Settings = require('../../src/store/settings');

describe('settings-store', () => {
  it('should create and get settings', async () => {
    const store = new Settings();
    store.clearAll();

    let settings = store.getAll();
    expect(settings.logSyncConfig).toEqual({});
    expect(settings.genAIUsageDisclaimer).toEqual(false);

    // adding a collection whose directory doesn't exist
    store.addLogSyncConfig(true, 'http://localhost:3000', 'access_id', 'access_key');
    store.addGenAIUsageDisclaimer(true);

    settings = store.getAll();
    const config = settings.logSyncConfig;
    expect(config.enabled).toEqual(true);
    expect(config.hostUrl).toEqual('http://localhost:3000');
    expect(config.accessId).toEqual('access_id');
    expect(config.accessKey).toEqual('access_key');

    expect(settings.genAIUsageDisclaimer).toEqual(true);
  });
});


================================================
FILE: packages/flowtest-electron/tests/test.yaml
================================================
openapi: 3.0.3
info:
  title: Swagger Petstore - OpenAPI 3.0
  description: |-
    This is a sample Pet Store Server based on the OpenAPI 3.0 specification.  You can find out more about
    Swagger at [https://swagger.io](https://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!
    You can now help us improve the API whether it's by making changes to the definition itself or to the code.
    That way, with time, we can improve the API in general, and expose some of the new features in OAS3.

    _If you're looking for the Swagger 2.0/OAS 2.0 version of Petstore, then click [here](https://editor.swagger.io/?url=https://petstore.swagger.io/v2/swagger.yaml). Alternatively, you can load via the `Edit > Load Petstore OAS 2.0` menu option!_
    
    Some useful links:
    - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)
    - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)
  termsOfService: http://swagger.io/terms/
  contact:
    email: apiteam@swagger.io
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: 1.0.11
externalDocs:
  description: Find out more about Swagger
  url: http://swagger.io
servers:
  - url: https://petstore3.swagger.io/api/v3
tags:
  - name: pet
    description: Everything about your Pets
    externalDocs:
      description: Find out more
      url: http://swagger.io
  - name: store
    description: Access to Petstore orders
    externalDocs:
      description: Find out more about our store
      url: http://swagger.io
  - name: user
    description: Operations about user
paths:
  /pet:
    put:
      tags:
        - pet
      summary: Update an existing pet
      description: Update an existing pet by Id
      operationId: updatePet
      requestBody:
        description: Update an existent pet in the store
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Pet'
          application/xml:
            schema:
              $ref: '#/components/schemas/Pet'
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/Pet'
        required: true
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'          
            application/xml:
              schema:
                $ref: '#/components/schemas/Pet'
        '400':
          description: Invalid ID supplied
        '404':
          description: Pet not found
        '405':
          description: Validation exception
      security:
        - petstore_auth:
            - write:pets
            - read:pets
    post:
      tags:
        - pet
      summary: Add a new pet to the store
      description: Add a new pet to the store
      operationId: addPet
      requestBody:
        description: Create a new pet in the store
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Pet'
          application/xml:
            schema:
              $ref: '#/components/schemas/Pet'
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/Pet'
        required: true
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'          
            application/xml:
              schema:
                $ref: '#/components/schemas/Pet'
        '405':
          description: Invalid input
      security:
        - petstore_auth:
            - write:pets
            - read:pets
  /pet/findByStatus:
    get:
      tags:
        - pet
      summary: Finds Pets by status
      description: Multiple status values can be provided with comma separated strings
      operationId: findPetsByStatus
      parameters:
        - name: status
          in: query
          description: Status values that need to be considered for filter
          required: false
          explode: true
          schema:
            type: string
            default: available
            enum:
              - available
              - pending
              - sold
      responses:
        '200':
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Pet'          
            application/xml:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Pet'
        '400':
          description: Invalid status value
      security:
        - petstore_auth:
            - write:pets
            - read:pets
  /pet/findByTags:
    get:
      tags:
        - pet
      summary: Finds Pets by tags
      description: Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
      operationId: findPetsByTags
      parameters:
        - name: tags
          in: query
          description: Tags to filter by
          required: false
          explode: true
          schema:
            type: array
            items:
              type: string
      responses:
        '200':
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Pet'          
            application/xml:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Pet'
        '400':
          description: Invalid tag value
      security:
        - petstore_auth:
            - write:pets
            - read:pets
  /pet/{petId}:
    get:
      tags:
        - pet
      summary: Find pet by ID
      description: Returns a single pet
      operationId: getPetById
      parameters:
        - name: petId
          in: path
          description: ID of pet to return
          required: true
          schema:
            type: integer
            format: int64
      responses:
        '200':
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'          
            application/xml:
              schema:
                $ref: '#/components/schemas/Pet'
        '400':
          description: Invalid ID supplied
        '404':
          description: Pet not found
      security:
        - api_key: []
        - petstore_auth:
            - write:pets
            - read:pets
    post:
      tags:
        - pet
      summary: Updates a pet in the store with form data
      description: ''
      operationId: updatePetWithForm
      parameters:
        - name: petId
          in: path
          description: ID of pet that needs to be updated
          required: true
          schema:
            type: integer
            format: int64
        - name: name
          in: query
          description: Name of pet that needs to be updated
          schema:
            type: string
        - name: status
          in: query
          description: Status of pet that needs to be updated
          schema:
            type: string
      responses:
        '405':
          description: Invalid input
      security:
        - petstore_auth:
            - write:pets
            - read:pets
    delete:
      tags:
        - pet
      summary: Deletes a pet
      description: delete a pet
      operationId: deletePet
      parameters:
        - name: api_key
          in: header
          description: ''
          required: false
          schema:
            type: string
        - name: petId
          in: path
          description: Pet id to delete
          required: true
          schema:
            type: integer
            format: int64
      responses:
        '400':
          description: Invalid pet value
      security:
        - petstore_auth:
            - write:pets
            - read:pets
  /pet/{petId}/uploadImage:
    post:
      tags:
        - pet
      summary: uploads an image
      description: ''
      operationId: uploadFile
      parameters:
        - name: petId
          in: path
          description: ID of pet to update
          required: true
          schema:
            type: integer
            format: int64
        - name: additionalMetadata
          in: query
          description: Additional Metadata
          required: false
          schema:
            type: string
      requestBody:
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        '200':
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiResponse'
      security:
        - petstore_auth:
            - write:pets
            - read:pets
  /store/inventory:
    get:
      tags:
        - store
      summary: Returns pet inventories by status
      description: Returns a map of status codes to quantities
      operationId: getInventory
      responses:
        '200':
          description: successful operation
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: integer
                  format: int32
      security:
        - api_key: []
  /store/order:
    post:
      tags:
        - store
      summary: Place an order for a pet
      description: Place a new order in the store
      operationId: placeOrder
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Order'
          application/xml:
            schema:
              $ref: '#/components/schemas/Order'
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/Order'
      responses:
        '200':
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '405':
          description: Invalid input
  /store/order/{orderId}:
    get:
      tags:
        - store
      summary: Find purchase order by ID
      description: For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.
      operationId: getOrderById
      parameters:
        - name: orderId
          in: path
          description: ID of order that needs to be fetched
          required: true
          schema:
            type: integer
            format: int64
      responses:
        '200':
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'          
            application/xml:
              schema:
                $ref: '#/components/schemas/Order'
        '400':
          description: Invalid ID supplied
        '404':
          description: Order not found
    delete:
      tags:
        - store
      summary: Delete purchase order by ID
      description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
      operationId: deleteOrder
      parameters:
        - name: orderId
          in: path
          description: ID of the order that needs to be deleted
          required: true
          schema:
            type: integer
            format: int64
      responses:
        '400':
          description: Invalid ID supplied
        '404':
          description: Order not found
  /user:
    post:
      tags:
        - user
      summary: Create user
      description: This can only be done by the logged in user.
      operationId: createUser
      requestBody:
        description: Created user object
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/User'
          application/xml:
            schema:
              $ref: '#/components/schemas/User'
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/User'
      responses:
        default:
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
            application/xml:
              schema:
                $ref: '#/components/schemas/User'
  /user/createWithList:
    post:
      tags:
        - user
      summary: Creates list of users with given input array
      description: Creates list of users with given input array
      operationId: createUsersWithListInput
      requestBody:
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/User'
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'          
            application/xml:
              schema:
                $ref: '#/components/schemas/User'
        default:
          description: successful operation
  /user/login:
    get:
      tags:
        - user
      summary: Logs user into the system
      description: ''
      operationId: loginUser
      parameters:
        - name: username
          in: query
          description: The user name for login
          required: false
          schema:
            type: string
        - name: password
          in: query
          description: The password for login in clear text
          required: false
          schema:
            type: string
      responses:
        '200':
          description: successful operation
          headers:
            X-Rate-Limit:
              description: calls per hour allowed by the user
              schema:
                type: integer
                format: int32
            X-Expires-After:
              description: date in UTC when token expires
              schema:
                type: string
                format: date-time
          content:
            application/xml:
              schema:
                type: string
            application/json:
              schema:
                type: string
        '400':
          description: Invalid username/password supplied
  /user/logout:
    get:
      tags:
        - user
      summary: Logs out current logged in user session
      description: ''
      operationId: logoutUser
      parameters: []
      responses:
        default:
          description: successful operation
  /user/{username}:
    get:
      tags:
        - user
      summary: Get user by user name
      description: ''
      operationId: getUserByName
      parameters:
        - name: username
          in: path
          description: 'The name that needs to be fetched. Use user1 for testing. '
          required: true
          schema:
            type: string
      responses:
        '200':
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'          
            application/xml:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          description: Invalid username supplied
        '404':
          description: User not found
    put:
      tags:
        - user
      summary: Update user
      description: This can only be done by the logged in user.
      operationId: updateUser
      parameters:
        - name: username
          in: path
          description: name that need to be deleted
          required: true
          schema:
            type: string
      requestBody:
        description: Update an existent user in the store
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/User'
          application/xml:
            schema:
              $ref: '#/components/schemas/User'
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/User'
      responses:
        default:
          description: successful operation
    delete:
      tags:
        - user
      summary: Delete user
      description: This can only be done by the logged in user.
      operationId: deleteUser
      parameters:
        - name: username
          in: path
          description: The name that needs to be deleted
          required: true
          schema:
            type: string
      responses:
        '400':
          description: Invalid username supplied
        '404':
          description: User not found
components:
  schemas:
    Order:
      type: object
      properties:
        id:
          type: integer
          format: int64
          example: 10
        petId:
          type: integer
          format: int64
          example: 198772
        quantity:
          type: integer
          format: int32
          example: 7
        shipDate:
          type: string
          format: date-time
        status:
          type: string
          description: Order Status
          example: approved
          enum:
            - placed
            - approved
            - delivered
        complete:
          type: boolean
      xml:
        name: order
    Customer:
      type: object
      properties:
        id:
          type: integer
          format: int64
          example: 100000
        username:
          type: string
          example: fehguy
        address:
          type: array
          xml:
            name: addresses
            wrapped: true
          items:
            $ref: '#/components/schemas/Address'
      xml:
        name: customer
    Address:
      type: object
      properties:
        street:
          type: string
          example: 437 Lytton
        city:
          type: string
          example: Palo Alto
        state:
          type: string
          example: CA
        zip:
          type: string
          example: '94301'
      xml:
        name: address
    Category:
      type: object
      properties:
        id:
          type: integer
          format: int64
          example: 1
        name:
          type: string
          example: Dogs
      xml:
        name: category
    User:
      type: object
      properties:
        id:
          type: integer
          format: int64
          example: 10
        username:
          type: string
          example: theUser
        firstName:
          type: string
          example: John
        lastName:
          type: string
          example: James
        email:
          type: string
          example: john@email.com
        password:
          type: string
          example: '12345'
        phone:
          type: string
          example: '12345'
        userStatus:
          type: integer
          description: User Status
          format: int32
          example: 1
      xml:
        name: user
    Tag:
      type: object
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
      xml:
        name: tag
    Pet:
      required:
        - name
        - photoUrls
      type: object
      properties:
        id:
          type: integer
          format: int64
          example: 10
        name:
          type: string
          example: doggie
        category:
          $ref: '#/components/schemas/Category'
        photoUrls:
          type: array
          xml:
            wrapped: true
          items:
            type: string
            xml:
              name: photoUrl
        tags:
          type: array
          xml:
            wrapped: true
          items:
            $ref: '#/components/schemas/Tag'
        status:
          type: string
          description: pet status in the store
          enum:
            - available
            - pending
            - sold
      xml:
        name: pet
    ApiResponse:
      type: object
      properties:
        code:
          type: integer
          format: int32
        type:
          type: string
        message:
          type: string
      xml:
        name: '##default'
  requestBodies:
    Pet:
      description: Pet object that needs to be added to the store
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Pet'
        application/xml:
          schema:
            $ref: '#/components/schemas/Pet'
    UserArray:
      description: List of user object
      content:
        application/json:
          schema:
            type: array
            items:
              $ref: '#/components/schemas/User'
  securitySchemes:
    petstore_auth:
      type: oauth2
      flows:
        implicit:
          authorizationUrl: https://petstore3.swagger.io/oauth/authorize
          scopes:
            write:pets: modify pets in your account
            read:pets: read your pets
    api_key:
      type: apiKey
      name: api_key
      in: header

================================================
FILE: packages/flowtest-electron/tests/utils/collection-parser.test.js
================================================
const SwaggerParser = require('@apidevtools/swagger-parser');
const JsonRefs = require('json-refs');
const parseOpenAPISpec = require('../../src/utils/collection');

describe('parse', () => {
  it('should add do basic parsing', async () => {
    let api = await SwaggerParser.validate('tests/test.yaml');
    console.log('API name: %s, Version: %s', api.info.title, api.info.version);
    const resolvedSpec = await JsonRefs.resolveRefs(api);
    const nodes = parseOpenAPISpec(resolvedSpec.resolved);
    expect(nodes).toEqual(expected);
  });
});

const expected = [
  {
    url: 'https://petstore3.swagger.io/api/v3/pet',
    description: 'Update an existing pet',
    operationId: 'updatePet',
    requestType: 'PUT',
  },
  {
    url: 'https://petstore3.swagger.io/api/v3/pet',
    description: 'Add a new pet to the store',
    operationId: 'addPet',
    requestType: 'POST',
  },
  {
    url: 'https://petstore3.swagger.io/api/v3/pet/findByStatus?status={{status}}',
    description: 'Finds Pets by status',
    operationId: 'findPetsByStatus',
    requestType: 'GET',
  },
  {
    url: 'https://petstore3.swagger.io/api/v3/pet/findByTags?tags={{tags}}',
    description: 'Finds Pets by tags',
    operationId: 'findPetsByTags',
    requestType: 'GET',
  },
  {
    url: 'https://petstore3.swagger.io/api/v3/pet/{{petId}}',
    description: 'Find pet by ID',
    operationId: 'getPetById',
    requestType: 'GET',
  },
  {
    url: 'https://petstore3.swagger.io/api/v3/pet/{{petId}}',
    description: 'Updates a pet in the store with form data',
    operationId: 'updatePetWithForm',
    requestType: 'POST',
  },
  {
    url: 'https://petstore3.swagger.io/api/v3/pet/{{petId}}',
    description: 'Deletes a pet',
    operationId: 'deletePet',
    requestType: 'DELETE',
  },
  {
    url: 'https://petstore3.swagger.io/api/v3/pet/{{petId}}/uploadImage',
    description: 'uploads an image',
    operationId: 'uploadFile',
    requestType: 'POST',
  },
  {
    url: 'https://petstore3.swagger.io/api/v3/store/inventory',
    description: 'Returns pet inventories by status',
    operationId: 'getInventory',
    requestType: 'GET',
  },
  {
    url: 'https://petstore3.swagger.io/api/v3/store/order',
    description: 'Place an order for a pet',
    operationId: 'placeOrder',
    requestType: 'POST',
  },
  {
    url: 'https://petstore3.swagger.io/api/v3/store/order/{{orderId}}',
    description: 'Find purchase order by ID',
    operationId: 'getOrderById',
    requestType: 'GET',
  },
  {
    url: 'https://petstore3.swagger.io/api/v3/store/order/{{orderId}}',
    description: 'Delete purchase order by ID',
    operationId: 'deleteOrder',
    requestType: 'DELETE',
  },
  {
    url: 'https://petstore3.swagger.io/api/v3/user',
    description: 'Create user',
    operationId: 'createUser',
    requestType: 'POST',
  },
  {
    url: 'https://petstore3.swagger.io/api/v3/user/createWithList',
    description: 'Creates list of users with given input array',
    operationId: 'createUsersWithListInput',
    requestType: 'POST',
  },
  {
    url: 'https://petstore3.swagger.io/api/v3/user/login?username={{username}}&password={{password}}',
    description: 'Logs user into the system',
    operationId: 'loginUser',
    requestType: 'GET',
  },
  {
    url: 'https://petstore3.swagger.io/api/v3/user/logout',
    description: 'Logs out current logged in user session',
    operationId: 'logoutUser',
    requestType: 'GET',
  },
  {
    url: 'https://petstore3.swagger.io/api/v3/user/{{username}}',
    description: 'Get user by user name',
    operationId: 'getUserByName',
    requestType: 'GET',
  },
  {
    url: 'https://petstore3.swagger.io/api/v3/user/{{username}}',
    description: 'Update user',
    operationId: 'updateUser',
    requestType: 'PUT',
  },
  {
    url: 'https://petstore3.swagger.io/api/v3/user/{{username}}',
    description: 'Delete user',
    operationId: 'deleteUser',
    requestType: 'DELETE',
  },
];


================================================
FILE: packages/flowtest-electron/tests/utils/filemanager.test.js
================================================
const createDirectory = require('../../src/utils/filemanager/createdirectory');
const deleteDirectory = require('../../src/utils/filemanager/deletedirectory');
const path = require('path');
const createFile = require('../../src/utils/filemanager/createfile');
const readFile = require('../../src/utils/filemanager/readfile');
const deleteFile = require('../../src/utils/filemanager/deletefile');
const { pathExists } = require('../../src/utils/filemanager/filesystem');

const DIRECTORY_NAME = 'testDir';

describe('file-manager', () => {
  it('should create and delete directory', async () => {
    let result = createDirectory(DIRECTORY_NAME, __dirname);
    expect(result).toEqual(path.join(__dirname, DIRECTORY_NAME));

    // directory already exists
    expect(() => {
      createDirectory(DIRECTORY_NAME, __dirname);
    }).toThrow(Error);

    deleteDirectory(path.join(__dirname, DIRECTORY_NAME));
    expect(pathExists(path.join(__dirname, DIRECTORY_NAME))).toEqual(false);

    // directory doesn't exist
    expect(() => {
      deleteDirectory(path.join(__dirname, DIRECTORY_NAME));
    }).toThrow(Error);
  });

  it('should create and delete files', async () => {
    let result = createDirectory(DIRECTORY_NAME, __dirname);
    expect(result).toEqual(path.join(__dirname, DIRECTORY_NAME));

    createFile('test.flow', path.join(__dirname, DIRECTORY_NAME), '{"k1":"v1"}');
    expect(pathExists(path.join(__dirname, DIRECTORY_NAME, 'test.flow'))).toEqual(true);

    // read file
    const rContent = readFile(path.join(__dirname, DIRECTORY_NAME, 'test.flow'));
    expect(rContent).toEqual('{"k1":"v1"}');

    // file already exists
    expect(() => {
      createFile('test.flow', path.join(__dirname, DIRECTORY_NAME), '{"k1":"v1"}');
    }).toThrow(Error);

    createFile('test1.flow', path.join(__dirname, DIRECTORY_NAME), '{"k1":"v1"}');
    expect(pathExists(path.join(__dirname, DIRECTORY_NAME, 'test1.flow'))).toEqual(true);

    // delete file
    deleteFile(path.join(__dirname, DIRECTORY_NAME, 'test1.flow'));
    expect(pathExists(path.join(__dirname, DIRECTORY_NAME, 'test1.flow'))).toEqual(false);

    // delete file: file no longer exists
    expect(() => {
      deleteFile(path.join(__dirname, DIRECTORY_NAME, 'test1.flow'));
    }).toThrow(Error);

    // delete directory
    deleteDirectory(path.join(__dirname, DIRECTORY_NAME));
    expect(pathExists(path.join(__dirname, DIRECTORY_NAME))).toEqual(false);
  });
});


================================================
FILE: packages/flowtest-electron/tests/utils/flowtest-ai.test.js
================================================
const fs = require('fs');
const SwaggerParser = require('@apidevtools/swagger-parser');
const JsonRefs = require('json-refs');
const FlowtestAI = require('../../src/ai/flowtestai');

describe('generate', () => {
  it('should generate functions using openai', async () => {
    const f = new FlowtestAI();
    const USER_INSTRUCTION =
      'Add a new pet to the store. \
            Then get the created pet. \
            Then get pet with status as available.';
    //const testYaml = fs.readFileSync('tests/test.yaml', { encoding: 'utf8', flag: 'r' });
    let api = await SwaggerParser.validate('tests/test.yaml');
    console.log('API name: %s, Version: %s', api.info.title, api.info.version);
    const resolvedSpec = (await JsonRefs.resolveRefs(api)).resolved;

    let result = await f.generate(resolvedSpec, USER_INSTRUCTION, {
      name: 'OPENAI',
      apiKey: '',
    });
    const nodeNames = result.map((node) => node.name);
    expect(nodeNames).toEqual(['addPet', 'getPetById', 'findPetsByStatus']);
  }, 60000);

  it('should generate functions using bedrock', async () => {
    const f = new FlowtestAI();
    const USER_INSTRUCTION =
      'Add a new pet to the store. \
            Then get the created pet. \
            Then get pet with status as available.';
    //const testYaml = fs.readFileSync('tests/test.yaml', { encoding: 'utf8', flag: 'r' });
    let api = await SwaggerParser.validate('tests/test.yaml');
    console.log('API name: %s, Version: %s', api.info.title, api.info.version);
    const resolvedSpec = (await JsonRefs.resolveRefs(api)).resolved;

    let result = await f.generate(resolvedSpec, USER_INSTRUCTION, {
      name: 'BEDROCK_CLAUDE',
      apiKey: {
        accessKeyId: '',
        secretAccessKey: '',
      },
    });
    const nodeNames = result.map((node) => node.name);
    expect(nodeNames).toEqual(['addPet', 'getPetById', 'findPetsByStatus']);
  }, 60000);

  it('should generate functions using gemini', async () => {
    const f = new FlowtestAI();
    const USER_INSTRUCTION =
      'Add a new pet to the store. \
            Then get the created pet. \
            Then get pet with status as available.';
    //const testYaml = fs.readFileSync('tests/test.yaml', { encoding: 'utf8', flag: 'r' });
    let api = await SwaggerParser.validate('tests/test.yaml');
    console.log('API name: %s, Version: %s', api.info.title, api.info.version);
    const resolvedSpec = (await JsonRefs.resolveRefs(api)).resolved;

    let result = await f.generate(resolvedSpec, USER_INSTRUCTION, {
      name: 'GEMINI',
      apiKey: '',
    });
    const nodeNames = result.map((node) => node.name);
    expect(nodeNames).toEqual(['addPet', 'getPetById', 'findPetsByStatus']);
  }, 60000);
});


================================================
FILE: packages/flowtest-electron/tests/utils/flowtest-parser.test.js
================================================
const { deserialize, serialize } = require('../../src/utils/flowparser/parser');

describe('FlowTest parser', () => {
  it('should parse correctly', async () => {
    const flowData = {
      nodes: [
        {
          id: '0',
          type: 'startNode',
          position: {
            x: 150,
            y: 150,
          },
          deletable: false,
          width: 90,
          height: 60,
        },
        {
          id: '1',
          data: {
            type: 'basic-auth',
            username: '{{accessId}}',
            password: '{{accessKey}}',
   
Download .txt
gitextract_hj9fjt_a/

├── .changeset/
│   ├── README.md
│   ├── config.json
│   ├── early-colts-approve.md
│   ├── honest-bags-fail.md
│   └── proud-ants-flash.md
├── .eslintignore
├── .eslintrc.js
├── .github/
│   └── CODEOWNERS
├── .gitignore
├── .npmrc
├── .prettierrc
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── jsconfig.json
├── package.json
├── packages/
│   ├── flowtest-cli/
│   │   ├── LICENSE.md
│   │   ├── README.md
│   │   ├── bin/
│   │   │   ├── axiosClient.js
│   │   │   └── index.js
│   │   ├── graph/
│   │   │   ├── Graph.js
│   │   │   ├── GraphLogger.js
│   │   │   ├── compute/
│   │   │   │   ├── assertnode.js
│   │   │   │   ├── authnode.js
│   │   │   │   ├── node.js
│   │   │   │   ├── requestnode.js
│   │   │   │   ├── setvarnode.js
│   │   │   │   ├── utils.js
│   │   │   │   └── utils.test.js
│   │   │   └── constants/
│   │   │       ├── assertOperators.js
│   │   │       └── evaluateOperators.js
│   │   ├── package.json
│   │   └── utils/
│   │       ├── flowparser/
│   │       │   ├── AssertNode.js
│   │       │   ├── AuthNode.js
│   │       │   ├── DelayNode.js
│   │       │   ├── NestedFlowNode.js
│   │       │   ├── Node.js
│   │       │   ├── OutputNode.js
│   │       │   ├── RequestNode.js
│   │       │   ├── SetVarNode.js
│   │       │   ├── StartNode.js
│   │       │   └── parser.js
│   │       └── readfile.js
│   └── flowtest-electron/
│       ├── .npmrc
│       ├── CHANGELOG.md
│       ├── assets/
│       │   └── MyIcon.icns
│       ├── electron-main.js
│       ├── electron-menu.js
│       ├── notarize.js
│       ├── package.json
│       ├── preload.js
│       ├── src/
│       │   ├── ai/
│       │   │   ├── flowtestai.js
│       │   │   └── models/
│       │   │       ├── bedrock_claude.js
│       │   │       ├── gemini.js
│       │   │       └── openai.js
│       │   ├── app/
│       │   │   └── watcher.js
│       │   ├── ipc/
│       │   │   ├── axiosClient.js
│       │   │   ├── collection.js
│       │   │   └── settings.js
│       │   ├── store/
│       │   │   ├── collection.js
│       │   │   └── settings.js
│       │   └── utils/
│       │       ├── collection.js
│       │       ├── collection.test.js
│       │       ├── filemanager/
│       │       │   ├── createdirectory.js
│       │       │   ├── createfile.js
│       │       │   ├── deletedirectory.js
│       │       │   ├── deletefile.js
│       │       │   ├── filesystem.js
│       │       │   ├── readfile.js
│       │       │   └── updatefile.js
│       │       ├── flowparser/
│       │       │   ├── AssertNode.js
│       │       │   ├── AuthNode.js
│       │       │   ├── DelayNode.js
│       │       │   ├── NestedFlowNode.js
│       │       │   ├── Node.js
│       │       │   ├── OutputNode.js
│       │       │   ├── RequestNode.js
│       │       │   ├── SetVarNode.js
│       │       │   ├── StartNode.js
│       │       │   └── parser.js
│       │       ├── generate-request-body.js
│       │       └── generate-request-parameters.js
│       └── tests/
│           ├── store/
│           │   ├── collection-store.test.js
│           │   └── settings-store.test.js
│           ├── test.yaml
│           ├── utils/
│           │   ├── collection-parser.test.js
│           │   ├── filemanager.test.js
│           │   ├── flowtest-ai.test.js
│           │   └── flowtest-parser.test.js
│           └── watcher.test.js
├── pnpm-workspace.yaml
├── postcss.config.js
├── public/
│   ├── index.html
│   ├── manifest.json
│   └── robots.txt
├── src/
│   ├── App.css
│   ├── App.js
│   ├── App.test.js
│   ├── components/
│   │   ├── atoms/
│   │   │   ├── EditableTextItem.js
│   │   │   ├── Editor.js
│   │   │   ├── Logo.js
│   │   │   ├── SelectAuthKeys.js
│   │   │   ├── SelectEnvironment.js
│   │   │   ├── Tabs.js
│   │   │   ├── ThemeController.js
│   │   │   ├── common/
│   │   │   │   ├── Button.js
│   │   │   │   ├── HomeLoadingScreen.js
│   │   │   │   ├── HorizontalDivider.js
│   │   │   │   ├── LoadingSpinner.js
│   │   │   │   ├── NumberInput.js
│   │   │   │   ├── TextEditor.js
│   │   │   │   ├── TextInput.js
│   │   │   │   ├── TextInputWithLabel.js
│   │   │   │   └── TimeoutSelector.js
│   │   │   ├── flow/
│   │   │   │   ├── FlowNode.js
│   │   │   │   ├── NodeHorizontalDivider.js
│   │   │   │   └── Textarea.js
│   │   │   ├── sidebar/
│   │   │   │   ├── collections/
│   │   │   │   │   └── OptionsMenu.js
│   │   │   │   └── environments/
│   │   │   │       └── EnvOptionsMenu.js
│   │   │   └── util.js
│   │   ├── layouts/
│   │   │   ├── SplitPane.js
│   │   │   └── WithoutSplitPane.js
│   │   ├── molecules/
│   │   │   ├── environment/
│   │   │   │   └── index.js
│   │   │   ├── flow/
│   │   │   │   ├── AddNodes.js
│   │   │   │   ├── constants/
│   │   │   │   │   ├── assertOperators.js
│   │   │   │   │   ├── evaluateOperators.js
│   │   │   │   │   └── requestNodes.js
│   │   │   │   ├── edges/
│   │   │   │   │   └── ButtonEdge.js
│   │   │   │   ├── flowtestai.js
│   │   │   │   ├── graph/
│   │   │   │   │   ├── Graph.js
│   │   │   │   │   ├── GraphLogger.js
│   │   │   │   │   ├── GraphRun.js
│   │   │   │   │   └── compute/
│   │   │   │   │       ├── assertnode.js
│   │   │   │   │       ├── authnode.js
│   │   │   │   │       ├── nestedflownode.js
│   │   │   │   │       ├── node.js
│   │   │   │   │       ├── requestnode.js
│   │   │   │   │       ├── setvarnode.js
│   │   │   │   │       ├── utils.js
│   │   │   │   │       └── utils.test.js
│   │   │   │   ├── index.js
│   │   │   │   ├── nodes/
│   │   │   │   │   ├── AssertNode.js
│   │   │   │   │   ├── AuthNode.js
│   │   │   │   │   ├── DelayNode.js
│   │   │   │   │   ├── FormDataSelector.js
│   │   │   │   │   ├── NestedFlowNode.js
│   │   │   │   │   ├── OutputNode.js
│   │   │   │   │   ├── RequestBody.js
│   │   │   │   │   ├── RequestNode.js
│   │   │   │   │   └── SetVarNode.js
│   │   │   │   └── utils.js
│   │   │   ├── footers/
│   │   │   │   └── MainFooter.js
│   │   │   ├── headers/
│   │   │   │   ├── MainHeader.js
│   │   │   │   ├── SideBarHeader.js
│   │   │   │   ├── SideBarSubHeader.js
│   │   │   │   ├── TabPanelHeader.js
│   │   │   │   └── WorkspaceHeader.js
│   │   │   ├── modals/
│   │   │   │   ├── AddEnvVariableModal.js
│   │   │   │   ├── ConfirmActionModal.js
│   │   │   │   ├── EditEnvVariableModal.js
│   │   │   │   ├── GenAIUsageDisclaimer.js
│   │   │   │   ├── GenerateFlowTestModal.js
│   │   │   │   ├── ImportCollectionModal.js
│   │   │   │   ├── OpenCollectionModal.js
│   │   │   │   ├── OutputNodeExpandedModal.js
│   │   │   │   ├── SaveFlowModal.js
│   │   │   │   ├── SettingsModal.js
│   │   │   │   ├── create/
│   │   │   │   │   └── NewCollectionModal.js
│   │   │   │   ├── flow/
│   │   │   │   │   ├── AddVariableModal.js
│   │   │   │   │   └── NewFlowTestModal.js
│   │   │   │   └── sidebar/
│   │   │   │       ├── NewEnvironmentFileModal.js
│   │   │   │       └── NewLabelModal.js
│   │   │   ├── sideSheets/
│   │   │   │   └── FlowLogs.js
│   │   │   ├── sidebar/
│   │   │   │   ├── Empty.js
│   │   │   │   └── content/
│   │   │   │       ├── Collection.js
│   │   │   │       ├── Collections.js
│   │   │   │       ├── Environment.js
│   │   │   │       ├── Environments.js
│   │   │   │       └── index.js
│   │   │   └── workspace/
│   │   │       ├── EmptyWorkSpaceContent.js
│   │   │       └── WorkspaceContent.js
│   │   ├── organisms/
│   │   │   ├── AppNavBar.js
│   │   │   ├── SideBar.js
│   │   │   └── workspace/
│   │   │       └── Workspace.js
│   │   └── pages/
│   │       └── Home.js
│   ├── constants/
│   │   ├── AppNavBar.js
│   │   ├── Common.js
│   │   ├── ImportCollectionTypes.js
│   │   ├── ModalNames.js
│   │   ├── WorkspaceDirectory.js
│   │   └── sidebar/
│   │       └── Environnments.js
│   ├── index.css
│   ├── index.js
│   ├── ipc/
│   │   ├── collection.js
│   │   └── settings.js
│   ├── reportWebVitals.js
│   ├── routes/
│   │   ├── Main.js
│   │   └── index.js
│   ├── service/
│   │   ├── collection.js
│   │   └── settings.js
│   ├── setupTests.js
│   ├── stores/
│   │   ├── AppNavBarStore.js
│   │   ├── CanvasStore.js
│   │   ├── CollectionStore.js
│   │   ├── CommonStore.js
│   │   ├── EnvStore.js
│   │   ├── EventListenerStore.js
│   │   ├── SettingsStore.js
│   │   ├── TabStore.js
│   │   ├── collectionstore.test.js
│   │   ├── eventstore.test.js
│   │   ├── tabstore.test.js
│   │   └── utils.js
│   └── utils/
│       ├── common.js
│       ├── useRenderCount.js
│       └── useTelemetry.js
└── tailwind.config.js
Download .txt
SYMBOL INDEX (211 symbols across 55 files)

FILE: packages/flowtest-cli/bin/index.js
  function bytesToBase64 (line 22) | function bytesToBase64(bytes) {

FILE: packages/flowtest-cli/graph/Graph.js
  class nestedFlowNode (line 15) | class nestedFlowNode extends Node {
    method constructor (line 16) | constructor(nodes, edges, startTime, timeout, initialEnvVars, logger) {
    method evaluate (line 25) | async evaluate() {
  class Graph (line 30) | class Graph {
    method constructor (line 31) | constructor(nodes, edges, startTime, timeout, initialEnvVars, logger) {
    method #checkTimeout (line 42) | #checkTimeout() {
    method #computeConnectingEdge (line 46) | #computeConnectingEdge(node, result) {
    method #computeDataFromPreviousNodes (line 64) | #computeDataFromPreviousNodes(node) {
    method #computeNode (line 80) | async #computeNode(node) {
    method run (line 245) | async run() {

FILE: packages/flowtest-cli/graph/GraphLogger.js
  class GraphLogger (line 7) | class GraphLogger {
    method constructor (line 8) | constructor() {
    method add (line 12) | add(logLevel, message, node) {
    method get (line 21) | get() {

FILE: packages/flowtest-cli/graph/compute/assertnode.js
  class assertNode (line 7) | class assertNode extends Node {
    method constructor (line 8) | constructor(operator, variables, prevNodeOutputData, envVariables, log...
    method getVariableValue (line 17) | getVariableValue(variable) {
    method evaluate (line 29) | evaluate() {

FILE: packages/flowtest-cli/graph/compute/authnode.js
  class authNode (line 6) | class authNode extends Node {
    method constructor (line 7) | constructor(nodeData, envVariables, logger) {
    method evaluate (line 13) | evaluate() {

FILE: packages/flowtest-cli/graph/compute/node.js
  class Node (line 1) | class Node {
    method constructor (line 2) | constructor(type) {
    method evaluate (line 6) | evaluate() {

FILE: packages/flowtest-cli/graph/compute/requestnode.js
  class requestNode (line 25) | class requestNode extends Node {
    method constructor (line 26) | constructor(nodeData, prevNodeOutputData, envVariables, auth, logger) {
    method evaluate (line 35) | async evaluate() {
    method formulateRequest (line 102) | formulateRequest(finalUrl, variablesDict) {
    method runHttpRequest (line 146) | async runHttpRequest(request) {

FILE: packages/flowtest-cli/graph/compute/setvarnode.js
  class setVarNode (line 5) | class setVarNode extends Node {
    method constructor (line 6) | constructor(nodeData, prevNodeOutputData, envVariables) {
    method getVariableValue (line 13) | getVariableValue(variable) {
    method evaluate (line 25) | evaluate() {

FILE: packages/flowtest-cli/utils/flowparser/AssertNode.js
  class AssertNode (line 3) | class AssertNode extends Node {
    method constructor (line 4) | constructor() {
    method serialize (line 8) | serialize(id, data, metadata) {
    method deserialize (line 17) | deserialize(node) {

FILE: packages/flowtest-cli/utils/flowparser/AuthNode.js
  class AuthNode (line 3) | class AuthNode extends Node {
    method constructor (line 4) | constructor() {
    method serialize (line 8) | serialize(id, data, metadata) {
    method deserialize (line 17) | deserialize(node) {

FILE: packages/flowtest-cli/utils/flowparser/DelayNode.js
  class DelayNode (line 3) | class DelayNode extends Node {
    method constructor (line 4) | constructor() {
    method serialize (line 8) | serialize(id, data, metadata) {
    method deserialize (line 17) | deserialize(node) {

FILE: packages/flowtest-cli/utils/flowparser/NestedFlowNode.js
  class NestedFlowNode (line 3) | class NestedFlowNode extends Node {
    method constructor (line 4) | constructor() {
    method serialize (line 8) | serialize(id, data, metadata) {
    method deserialize (line 17) | deserialize(node) {

FILE: packages/flowtest-cli/utils/flowparser/Node.js
  class Node (line 1) | class Node {
    method constructor (line 2) | constructor(type) {
    method serialize (line 6) | serialize(id, data, metadata) {
    method deserialize (line 10) | deserialize(node) {

FILE: packages/flowtest-cli/utils/flowparser/OutputNode.js
  class OutputNode (line 3) | class OutputNode extends Node {
    method constructor (line 4) | constructor() {
    method serialize (line 8) | serialize(id, data, metadata) {
    method deserialize (line 17) | deserialize(node) {

FILE: packages/flowtest-cli/utils/flowparser/RequestNode.js
  class RequestNode (line 3) | class RequestNode extends Node {
    method constructor (line 4) | constructor() {
    method serialize (line 8) | serialize(id, data, metadata) {
    method deserialize (line 17) | deserialize(node) {

FILE: packages/flowtest-cli/utils/flowparser/SetVarNode.js
  class SetVarNode (line 3) | class SetVarNode extends Node {
    method constructor (line 4) | constructor() {
    method serialize (line 8) | serialize(id, data, metadata) {
    method deserialize (line 17) | deserialize(node) {

FILE: packages/flowtest-cli/utils/flowparser/StartNode.js
  class StartNode (line 3) | class StartNode extends Node {
    method constructor (line 4) | constructor() {
    method serialize (line 8) | serialize(id, data, metadata) {
    method deserialize (line 15) | deserialize(node) {

FILE: packages/flowtest-cli/utils/flowparser/parser.js
  constant VERSION (line 11) | const VERSION = 1;

FILE: packages/flowtest-electron/electron-main.js
  function checkForUpdates (line 30) | function checkForUpdates() {

FILE: packages/flowtest-electron/src/ai/flowtestai.js
  class FlowtestAI (line 5) | class FlowtestAI {
    method generate (line 6) | async generate(collection, user_instruction, model) {
    method get_available_functions (line 27) | async get_available_functions(collection) {
    method isCyclic (line 85) | isCyclic(obj) {

FILE: packages/flowtest-electron/src/ai/models/bedrock_claude.js
  class BedrockClaudeGenerate (line 6) | class BedrockClaudeGenerate {
    method constructor (line 7) | constructor(creds) {
    method filter_functions (line 25) | async filter_functions(functions, instruction) {
    method process_user_instruction (line 47) | async process_user_instruction(functions, instruction) {
    method extractFunctionCalls (line 91) | extractFunctionCalls(completion) {

FILE: packages/flowtest-electron/src/ai/models/gemini.js
  class GeminiGenerate (line 6) | class GeminiGenerate {
    method constructor (line 7) | constructor(apiKey) {
    method filter_functions (line 18) | async filter_functions(functions, instruction) {
    method process_user_instruction (line 42) | async process_user_instruction(functions, instruction) {
    method extractFunctionCalls (line 96) | extractFunctionCalls(completion) {

FILE: packages/flowtest-electron/src/ai/models/openai.js
  constant SYSTEM_MESSAGE (line 5) | const SYSTEM_MESSAGE = `You are a helpful assistant. \
  constant MAX_CALLS (line 10) | const MAX_CALLS = 10;
  class OpenAIGenerate (line 12) | class OpenAIGenerate {
    method filter_functions (line 13) | async filter_functions(functions, instruction, apiKey) {
    method get_openai_response (line 43) | async get_openai_response(functions, messages, apiKey) {
    method process_user_instruction (line 57) | async process_user_instruction(functions, instruction, apiKey) {

FILE: packages/flowtest-electron/src/app/watcher.js
  class Watcher (line 8) | class Watcher {
    method constructor (line 9) | constructor() {
    method isFlowTestFile (line 13) | isFlowTestFile(pathname) {
    method isEnvFile (line 18) | isEnvFile(pathname, collectionPath) {
    method isDotEnvFile (line 26) | isDotEnvFile(pathname, collectionPath) {
    method add (line 33) | add(mainWindow, pathname, collectionId, watchPath) {
    method addDirectory (line 70) | addDirectory(mainWindow, pathname, collectionId, watchPath) {
    method change (line 92) | change(mainWindow, pathname, collectionId, watchPath) {
    method unlink (line 125) | unlink(mainWindow, pathname, collectionId, watchPath) {
    method unlinkDir (line 146) | unlinkDir(mainWindow, pathname, collectionId, watchPath) {
    method getEnvVariables (line 161) | getEnvVariables(pathname) {
    method addWatcher (line 168) | addWatcher(mainWindow, watchPath, collectionId) {
    method hasWatcher (line 201) | hasWatcher(watchPath) {
    method removeWatcher (line 205) | removeWatcher(watchPath) {

FILE: packages/flowtest-electron/src/ipc/collection.js
  function bytesToBase64 (line 379) | function bytesToBase64(bytes) {

FILE: packages/flowtest-electron/src/store/collection.js
  class Collections (line 4) | class Collections {
    method constructor (line 5) | constructor() {
    method add (line 9) | add(collection) {
    method remove (line 20) | remove(collection) {
    method getAll (line 31) | getAll() {
    method removeAll (line 35) | removeAll() {

FILE: packages/flowtest-electron/src/store/settings.js
  class Settings (line 3) | class Settings {
    method constructor (line 4) | constructor() {
    method addLogSyncConfig (line 8) | addLogSyncConfig(enabled, hostUrl, accessId, accessKey) {
    method addGenAIUsageDisclaimer (line 12) | addGenAIUsageDisclaimer(accepted) {
    method getAll (line 16) | getAll() {
    method clearAll (line 23) | clearAll() {

FILE: packages/flowtest-electron/src/utils/filemanager/filesystem.js
  constant PATH_SEPARATOR (line 52) | const PATH_SEPARATOR = isWindowsOS() ? '\\' : '/';

FILE: packages/flowtest-electron/src/utils/flowparser/AssertNode.js
  class AssertNode (line 3) | class AssertNode extends Node {
    method constructor (line 4) | constructor() {
    method serialize (line 8) | serialize(id, data, metadata) {
    method deserialize (line 17) | deserialize(node) {

FILE: packages/flowtest-electron/src/utils/flowparser/AuthNode.js
  class AuthNode (line 3) | class AuthNode extends Node {
    method constructor (line 4) | constructor() {
    method serialize (line 8) | serialize(id, data, metadata) {
    method deserialize (line 17) | deserialize(node) {

FILE: packages/flowtest-electron/src/utils/flowparser/DelayNode.js
  class DelayNode (line 3) | class DelayNode extends Node {
    method constructor (line 4) | constructor() {
    method serialize (line 8) | serialize(id, data, metadata) {
    method deserialize (line 17) | deserialize(node) {

FILE: packages/flowtest-electron/src/utils/flowparser/NestedFlowNode.js
  class NestedFlowNode (line 3) | class NestedFlowNode extends Node {
    method constructor (line 4) | constructor() {
    method serialize (line 8) | serialize(id, data, metadata) {
    method deserialize (line 17) | deserialize(node) {

FILE: packages/flowtest-electron/src/utils/flowparser/Node.js
  class Node (line 1) | class Node {
    method constructor (line 2) | constructor(type) {
    method serialize (line 6) | serialize(id, data, metadata) {
    method deserialize (line 10) | deserialize(node) {

FILE: packages/flowtest-electron/src/utils/flowparser/OutputNode.js
  class OutputNode (line 3) | class OutputNode extends Node {
    method constructor (line 4) | constructor() {
    method serialize (line 8) | serialize(id, data, metadata) {
    method deserialize (line 17) | deserialize(node) {

FILE: packages/flowtest-electron/src/utils/flowparser/RequestNode.js
  class RequestNode (line 3) | class RequestNode extends Node {
    method constructor (line 4) | constructor() {
    method serialize (line 8) | serialize(id, data, metadata) {
    method deserialize (line 17) | deserialize(node) {

FILE: packages/flowtest-electron/src/utils/flowparser/SetVarNode.js
  class SetVarNode (line 3) | class SetVarNode extends Node {
    method constructor (line 4) | constructor() {
    method serialize (line 8) | serialize(id, data, metadata) {
    method deserialize (line 17) | deserialize(node) {

FILE: packages/flowtest-electron/src/utils/flowparser/StartNode.js
  class StartNode (line 3) | class StartNode extends Node {
    method constructor (line 4) | constructor() {
    method serialize (line 8) | serialize(id, data, metadata) {
    method deserialize (line 15) | deserialize(node) {

FILE: packages/flowtest-electron/src/utils/flowparser/parser.js
  constant VERSION (line 11) | const VERSION = 1;

FILE: packages/flowtest-electron/src/utils/generate-request-parameters.js
  function generateParameterExample (line 1) | function generateParameterExample(parameter) {
  function generateStringExample (line 26) | function generateStringExample(schema) {
  function generateStringWithLengthConstraints (line 61) | function generateStringWithLengthConstraints(str, minLength, maxLength) {
  function generateIntegerExample (line 73) | function generateIntegerExample(schema) {
  function generateNumberExample (line 79) | function generateNumberExample(schema) {
  function generateArrayExample (line 85) | function generateArrayExample(schema) {

FILE: packages/flowtest-electron/tests/utils/filemanager.test.js
  constant DIRECTORY_NAME (line 9) | const DIRECTORY_NAME = 'testDir';

FILE: src/App.js
  function App (line 6) | function App() {

FILE: src/components/atoms/common/TextEditor.js
  method constructor (line 85) | constructor(view) {
  method update (line 88) | update(update) {

FILE: src/components/molecules/flow/edges/ButtonEdge.js
  function CustomEdge (line 9) | function CustomEdge({

FILE: src/components/molecules/flow/graph/Graph.js
  class Graph (line 13) | class Graph {
    method constructor (line 14) | constructor(nodes, edges, startTime, initialEnvVars, logger, collectio...
    method #checkTimeout (line 28) | #checkTimeout() {
    method #computeConnectingEdge (line 32) | #computeConnectingEdge(node, result) {
    method #computeDataFromPreviousNodes (line 50) | #computeDataFromPreviousNodes(node) {
    method #computeNode (line 66) | async #computeNode(node) {
    method run (line 239) | async run() {

FILE: src/components/molecules/flow/graph/GraphLogger.js
  class GraphLogger (line 7) | class GraphLogger {
    method constructor (line 8) | constructor() {
    method add (line 12) | add(logLevel, message, node) {
    method get (line 21) | get() {

FILE: src/components/molecules/flow/graph/compute/assertnode.js
  class assertNode (line 6) | class assertNode extends Node {
    method constructor (line 7) | constructor(operator, variables, prevNodeOutputData, envVariables, log...
    method getVariableValue (line 16) | getVariableValue(variable) {
    method evaluate (line 28) | evaluate() {

FILE: src/components/molecules/flow/graph/compute/authnode.js
  class authNode (line 5) | class authNode extends Node {
    method constructor (line 6) | constructor(nodeData, envVariables, logger) {
    method evaluate (line 12) | evaluate() {

FILE: src/components/molecules/flow/graph/compute/nestedflownode.js
  class nestedFlowNode (line 4) | class nestedFlowNode extends Node {
    method constructor (line 5) | constructor(nodes, edges, startTime, initialEnvVars, logger, collectio...
    method evaluate (line 10) | async evaluate() {

FILE: src/components/molecules/flow/graph/compute/node.js
  class Node (line 1) | class Node {
    method constructor (line 2) | constructor(type) {
    method evaluate (line 6) | evaluate() {

FILE: src/components/molecules/flow/graph/compute/requestnode.js
  class requestNode (line 6) | class requestNode extends Node {
    method constructor (line 7) | constructor(nodeData, prevNodeOutputData, envVariables, auth, logger, ...
    method evaluate (line 17) | async evaluate() {
    method formulateRequest (line 82) | async formulateRequest(finalUrl, variablesDict) {
    method runHttpRequest (line 126) | runHttpRequest(rawRequest) {

FILE: src/components/molecules/flow/graph/compute/setvarnode.js
  class setVarNode (line 5) | class setVarNode extends Node {
    method constructor (line 6) | constructor(nodeData, prevNodeOutputData, envVariables) {
    method getVariableValue (line 13) | getVariableValue(variable) {
    method evaluate (line 25) | evaluate() {

FILE: src/components/molecules/modals/GenerateFlowTestModal.js
  function gen (line 599) | function gen() {

FILE: src/constants/Common.js
  constant FLOW_FILE_SUFFIX_REGEX (line 1) | const FLOW_FILE_SUFFIX_REGEX = /^.+\.flow$/gm;
  constant CHOOSE_OPERATOR_DEFAULT_VALUE_OBJ (line 3) | const CHOOSE_OPERATOR_DEFAULT_VALUE_OBJ = {
  constant INPUT_DEFAULT_TYPE (line 8) | const INPUT_DEFAULT_TYPE = 'Text';
  constant BUTTON_TYPES (line 10) | const BUTTON_TYPES = {
  constant BUTTON_INTENT_TYPES (line 18) | const BUTTON_INTENT_TYPES = {
  constant OBJ_TYPES (line 25) | const OBJ_TYPES = {
  constant GENAI_MODELS (line 32) | const GENAI_MODELS = {

FILE: src/routes/index.js
  function Routes (line 4) | function Routes() {

FILE: src/stores/CollectionStore.js
  method deleteCollection (line 35) | deleteCollection(collectionId) {
Condensed preview — 217 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (686K chars).
[
  {
    "path": ".changeset/README.md",
    "chars": 510,
    "preview": "# Changesets\n\nHello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that wo"
  },
  {
    "path": ".changeset/config.json",
    "chars": 346,
    "preview": "{\n  \"$schema\": \"https://unpkg.com/@changesets/config@3.0.0/schema.json\",\n  \"changelog\": [\"@changesets/cli/changelog\", { "
  },
  {
    "path": ".changeset/early-colts-approve.md",
    "chars": 100,
    "preview": "---\n'flowtestai-app': minor\n---\n\nAdd support for google geminin function calling in generating flow\n"
  },
  {
    "path": ".changeset/honest-bags-fail.md",
    "chars": 72,
    "preview": "---\n'flowtestai-app': minor\n---\n\nadd support for bearer token auth type\n"
  },
  {
    "path": ".changeset/proud-ants-flash.md",
    "chars": 76,
    "preview": "---\n'flowtestai-app': minor\n---\n\nallow request headers to be input by users\n"
  },
  {
    "path": ".eslintignore",
    "chars": 85,
    "preview": "# dependencies\n**/node_modules\n\n# production\n/build\n\n# intermal dependencies\n/server\n"
  },
  {
    "path": ".eslintrc.js",
    "chars": 913,
    "preview": "module.exports = {\n  env: {\n    browser: true,\n    es2021: true,\n    jest: true,\n  },\n  extends: ['eslint:recommended', "
  },
  {
    "path": ".github/CODEOWNERS",
    "chars": 518,
    "preview": "# Lines starting with '#' are comments.\n# Each line is a file pattern followed by one or more owners.\n\n# More details ar"
  },
  {
    "path": ".gitignore",
    "chars": 429,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n**/node_modules\n/."
  },
  {
    "path": ".npmrc",
    "chars": 97,
    "preview": "node-linker=hoisted #pnpm requirement when working with electron\nignore-workspace-root-check=true"
  },
  {
    "path": ".prettierrc",
    "chars": 204,
    "preview": "{\n  \"semi\": true,\n  \"tabWidth\": 2,\n  \"printWidth\": 120,\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\",\n  \"jsxSingleQuo"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 3985,
    "preview": "# Contribution Guidelines\n\nWhen contributing to `FlowTestAI`, whether on GitHub or in other community spaces:\n\n- Be resp"
  },
  {
    "path": "LICENSE",
    "chars": 1067,
    "preview": "MIT License\n\nCopyright (c) 2023 Sajal Jain\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
  },
  {
    "path": "README.md",
    "chars": 7160,
    "preview": "# FlowTestAI: Streamlining End-to-End API Testing\n\n[![Release Notes](https://img.shields.io/github/release/FlowTestAI/Fl"
  },
  {
    "path": "jsconfig.json",
    "chars": 172,
    "preview": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"src\",\n    \"target\": \"es5\",\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n    "
  },
  {
    "path": "package.json",
    "chars": 2985,
    "preview": "{\n  \"name\": \"flowtest\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"homepage\": \".\",\n  \"packageManager\": \"pnpm@9.0.6\",\n  "
  },
  {
    "path": "packages/flowtest-cli/LICENSE.md",
    "chars": 1067,
    "preview": "MIT License\n\nCopyright (c) 2023 Sajal Jain\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
  },
  {
    "path": "packages/flowtest-cli/README.md",
    "chars": 1746,
    "preview": "# flowtestai-cli\n\nWith FlowTestAI CLI, you can now run your end to end flows, constructed using FlowTestAI, directly fro"
  },
  {
    "path": "packages/flowtest-cli/bin/axiosClient.js",
    "chars": 734,
    "preview": "// lib/axiosClient.ts\nconst axios = require('axios');\nconst axiosRetry = require('axios-retry').default;\n\nconst baseUrl "
  },
  {
    "path": "packages/flowtest-cli/bin/index.js",
    "chars": 5970,
    "preview": "#!/usr/bin/env node\n\nconst yargs = require('yargs/yargs');\nconst { hideBin } = require('yargs/helpers');\nconst chalk = r"
  },
  {
    "path": "packages/flowtest-cli/graph/Graph.js",
    "chars": 9184,
    "preview": "// assumption is that apis are giving json as output\n\nconst { cloneDeep } = require('lodash');\nconst authNode = require("
  },
  {
    "path": "packages/flowtest-cli/graph/GraphLogger.js",
    "chars": 399,
    "preview": "const LogLevel = Object.freeze({\n  INFO: 'info',\n  WARN: 'warn',\n  ERROR: 'error',\n});\n\nclass GraphLogger {\n  constructo"
  },
  {
    "path": "packages/flowtest-cli/graph/compute/assertnode.js",
    "chars": 2290,
    "preview": "const AssertOperators = require('../constants/assertOperators');\nconst { computeNodeVariable } = require('./utils');\ncon"
  },
  {
    "path": "packages/flowtest-cli/graph/compute/authnode.js",
    "chars": 1639,
    "preview": "const { computeVariables } = require('./utils');\nconst Node = require('./node');\nconst chalk = require('chalk');\nconst {"
  },
  {
    "path": "packages/flowtest-cli/graph/compute/node.js",
    "chars": 181,
    "preview": "class Node {\n  constructor(type) {\n    this.type = type;\n  }\n\n  evaluate() {\n    throw new Error('Evaluate method must b"
  },
  {
    "path": "packages/flowtest-cli/graph/compute/requestnode.js",
    "chars": 6582,
    "preview": "const { computeNodeVariables, computeVariables } = require('./utils');\nconst Node = require('./node');\nconst axios = req"
  },
  {
    "path": "packages/flowtest-cli/graph/compute/setvarnode.js",
    "chars": 2556,
    "preview": "const { computeNodeVariable } = require('./utils');\nconst Node = require('./node');\nconst EvaluateOperators = require('."
  },
  {
    "path": "packages/flowtest-cli/graph/compute/utils.js",
    "chars": 2616,
    "preview": "const computeNodeVariable = (variable, prevNodeOutputData) => {\n  if (variable.type.toLowerCase() === 'string') {\n    re"
  },
  {
    "path": "packages/flowtest-cli/graph/compute/utils.test.js",
    "chars": 884,
    "preview": "const { computeVariables } = require('./utils');\n\ndescribe('Utils', () => {\n  it('should compute variables correctly', ("
  },
  {
    "path": "packages/flowtest-cli/graph/constants/assertOperators.js",
    "chars": 184,
    "preview": "const AssertOperators = {\n  isLessThan: 'isLessThan',\n  isGreaterThan: 'isGreaterThan',\n  isEqualTo: 'isEqualTo',\n  isNo"
  },
  {
    "path": "packages/flowtest-cli/graph/constants/evaluateOperators.js",
    "chars": 130,
    "preview": "const EvaluateOperators = {\n  Add: 'Add two numbers',\n  Subtract: 'Subtract two numbers',\n};\n\nmodule.exports = EvaluateO"
  },
  {
    "path": "packages/flowtest-cli/package.json",
    "chars": 844,
    "preview": "{\n  \"name\": \"flowtestai\",\n  \"version\": \"1.0.2\",\n  \"description\": \"CLI to run flow from command line\",\n  \"main\": \"bin/ind"
  },
  {
    "path": "packages/flowtest-cli/utils/flowparser/AssertNode.js",
    "chars": 485,
    "preview": "const { Node } = require('./Node');\n\nclass AssertNode extends Node {\n  constructor() {\n    super('assertNode');\n  }\n\n  s"
  },
  {
    "path": "packages/flowtest-cli/utils/flowparser/AuthNode.js",
    "chars": 479,
    "preview": "const { Node } = require('./Node');\n\nclass AuthNode extends Node {\n  constructor() {\n    super('authNode');\n  }\n\n  seria"
  },
  {
    "path": "packages/flowtest-cli/utils/flowparser/DelayNode.js",
    "chars": 482,
    "preview": "const { Node } = require('./Node');\n\nclass DelayNode extends Node {\n  constructor() {\n    super('delayNode');\n  }\n\n  ser"
  },
  {
    "path": "packages/flowtest-cli/utils/flowparser/NestedFlowNode.js",
    "chars": 491,
    "preview": "const { Node } = require('./Node');\n\nclass NestedFlowNode extends Node {\n  constructor() {\n    super('flowNode');\n  }\n\n "
  },
  {
    "path": "packages/flowtest-cli/utils/flowparser/Node.js",
    "chars": 312,
    "preview": "class Node {\n  constructor(type) {\n    this.type = type;\n  }\n\n  serialize(id, data, metadata) {\n    throw new Error('Ser"
  },
  {
    "path": "packages/flowtest-cli/utils/flowparser/OutputNode.js",
    "chars": 507,
    "preview": "const { Node } = require('./Node');\n\nclass OutputNode extends Node {\n  constructor() {\n    super('outputNode');\n  }\n\n  s"
  },
  {
    "path": "packages/flowtest-cli/utils/flowparser/RequestNode.js",
    "chars": 488,
    "preview": "const { Node } = require('./Node');\n\nclass RequestNode extends Node {\n  constructor() {\n    super('requestNode');\n  }\n\n "
  },
  {
    "path": "packages/flowtest-cli/utils/flowparser/SetVarNode.js",
    "chars": 485,
    "preview": "const { Node } = require('./Node');\n\nclass SetVarNode extends Node {\n  constructor() {\n    super('setVarNode');\n  }\n\n  s"
  },
  {
    "path": "packages/flowtest-cli/utils/flowparser/StartNode.js",
    "chars": 385,
    "preview": "const { Node } = require('./Node');\n\nclass StartNode extends Node {\n  constructor() {\n    super('startNode');\n  }\n\n  ser"
  },
  {
    "path": "packages/flowtest-cli/utils/flowparser/parser.js",
    "chars": 7834,
    "preview": "const { cloneDeep } = require('lodash');\nconst { AuthNode } = require('./AuthNode');\nconst { NestedFlowNode } = require("
  },
  {
    "path": "packages/flowtest-cli/utils/readfile.js",
    "chars": 447,
    "preview": "const fs = require('fs');\n\nconst pathExists = (path) => {\n  try {\n    fs.accessSync(path);\n    return true;\n  } catch (e"
  },
  {
    "path": "packages/flowtest-electron/.npmrc",
    "chars": 64,
    "preview": "node-linker=hoisted #pnpm requirement when working with electron"
  },
  {
    "path": "packages/flowtest-electron/CHANGELOG.md",
    "chars": 1177,
    "preview": "# flowtestai\n\n## 1.2.0\n\n### Minor Changes\n\n- aadbd1b: Collapse sidebar to give more real estate to canvas\n- 780f3c2: gen"
  },
  {
    "path": "packages/flowtest-electron/electron-main.js",
    "chars": 3237,
    "preview": "// Modules to control application life and create native browser window\nconst { app, BrowserWindow, Menu, shell } = requ"
  },
  {
    "path": "packages/flowtest-electron/electron-menu.js",
    "chars": 1172,
    "preview": "const { shell } = require('electron');\n\nconst template = [\n  {\n    label: 'FlowTestAI',\n    submenu: [\n      { type: 'se"
  },
  {
    "path": "packages/flowtest-electron/notarize.js",
    "chars": 550,
    "preview": "require('dotenv').config();\nconst { notarize } = require('@electron/notarize');\n\nexports.default = async function notari"
  },
  {
    "path": "packages/flowtest-electron/package.json",
    "chars": 2484,
    "preview": "{\n  \"name\": \"flowtestai-app\",\n  \"productName\": \"FlowTestAI\",\n  \"version\": \"1.2.0\",\n  \"homepage\": \"https://github.com/Flo"
  },
  {
    "path": "packages/flowtest-electron/preload.js",
    "chars": 529,
    "preview": "const { ipcRenderer, contextBridge } = require('electron');\nconst path = require('path');\nconst { isMacOS } = require('."
  },
  {
    "path": "packages/flowtest-electron/src/ai/flowtestai.js",
    "chars": 4263,
    "preview": "const BedrockClaudeGenerate = require('./models/bedrock_claude');\nconst GeminiGenerate = require('./models/gemini');\ncon"
  },
  {
    "path": "packages/flowtest-electron/src/ai/models/bedrock_claude.js",
    "chars": 4829,
    "preview": "const { BedrockChat } = require('@langchain/community/chat_models/bedrock');\nconst { HumanMessage, SystemMessage, BaseMe"
  },
  {
    "path": "packages/flowtest-electron/src/ai/models/gemini.js",
    "chars": 4763,
    "preview": "const { GoogleGenerativeAI } = require('@google/generative-ai');\nconst { GoogleGenerativeAIEmbeddings } = require('@lang"
  },
  {
    "path": "packages/flowtest-electron/src/ai/models/openai.js",
    "chars": 3163,
    "preview": "const OpenAI = require('openai');\nconst { MemoryVectorStore } = require('langchain/vectorstores/memory');\nconst { OpenAI"
  },
  {
    "path": "packages/flowtest-electron/src/app/watcher.js",
    "chars": 7235,
    "preview": "const chokidar = require('chokidar');\nconst path = require('path');\nconst dotenv = require('dotenv');\nconst { PATH_SEPAR"
  },
  {
    "path": "packages/flowtest-electron/src/ipc/axiosClient.js",
    "chars": 839,
    "preview": "// lib/axiosClient.ts\nconst axios = require('axios');\nconst axiosRetry = require('axios-retry').default;\n\nconst axiosCli"
  },
  {
    "path": "packages/flowtest-electron/src/ipc/collection.js",
    "chars": 14009,
    "preview": "const fs = require('fs');\nconst path = require('path');\nconst axios = require('axios');\nconst { ipcMain, shell, dialog, "
  },
  {
    "path": "packages/flowtest-electron/src/ipc/settings.js",
    "chars": 1185,
    "preview": "const { ipcMain, shell, dialog, app } = require('electron');\nconst Settings = require('../store/settings');\n\nconst setti"
  },
  {
    "path": "packages/flowtest-electron/src/store/collection.js",
    "chars": 937,
    "preview": "const Store = require('electron-store');\nconst { isDirectory } = require('../utils/filemanager/filesystem');\n\nclass Coll"
  },
  {
    "path": "packages/flowtest-electron/src/store/settings.js",
    "chars": 670,
    "preview": "const Store = require('electron-store');\n\nclass Settings {\n  constructor() {\n    this.store = new Store();\n  }\n\n  addLog"
  },
  {
    "path": "packages/flowtest-electron/src/utils/collection.js",
    "chars": 4786,
    "preview": "const { generateRequestBodyExample } = require('./generate-request-body');\nconst {\n  generateQueryParamsExample,\n  gener"
  },
  {
    "path": "packages/flowtest-electron/src/utils/collection.test.js",
    "chars": 4452,
    "preview": "const { generateRequestBodyExample } = require('./generate-request-body.js');\nconst { generatePathParamsExample, generat"
  },
  {
    "path": "packages/flowtest-electron/src/utils/filemanager/createdirectory.js",
    "chars": 806,
    "preview": "const fs = require('fs');\nconst { isDirectory, pathExists } = require('./filesystem');\nconst path = require('path');\n\nco"
  },
  {
    "path": "packages/flowtest-electron/src/utils/filemanager/createfile.js",
    "chars": 747,
    "preview": "const fs = require('fs');\nconst dpath = require('path');\nconst { isDirectory, pathExists } = require('./filesystem');\n\nc"
  },
  {
    "path": "packages/flowtest-electron/src/utils/filemanager/deletedirectory.js",
    "chars": 427,
    "preview": "const fs = require('fs');\nconst { isDirectory } = require('./filesystem');\n\nconst deleteDirectory = (path) => {\n  if (!p"
  },
  {
    "path": "packages/flowtest-electron/src/utils/filemanager/deletefile.js",
    "chars": 392,
    "preview": "const fs = require('fs');\nconst { pathExists } = require('./filesystem');\n\nconst deleteFile = (path) => {\n  if (!path) {"
  },
  {
    "path": "packages/flowtest-electron/src/utils/filemanager/filesystem.js",
    "chars": 1395,
    "preview": "const fs = require('fs');\nconst path = require('path');\n\n/**\n * Determine if the given path is directory\n */\nconst isDir"
  },
  {
    "path": "packages/flowtest-electron/src/utils/filemanager/readfile.js",
    "chars": 368,
    "preview": "const fs = require('fs');\nconst { pathExists } = require('./filesystem');\n\nconst readFile = (path) => {\n  if (!path) {\n "
  },
  {
    "path": "packages/flowtest-electron/src/utils/filemanager/updatefile.js",
    "chars": 401,
    "preview": "const fs = require('fs');\nconst { pathExists } = require('./filesystem');\n\nconst upadateFile = (path, content) => {\n  if"
  },
  {
    "path": "packages/flowtest-electron/src/utils/flowparser/AssertNode.js",
    "chars": 485,
    "preview": "const { Node } = require('./Node');\n\nclass AssertNode extends Node {\n  constructor() {\n    super('assertNode');\n  }\n\n  s"
  },
  {
    "path": "packages/flowtest-electron/src/utils/flowparser/AuthNode.js",
    "chars": 479,
    "preview": "const { Node } = require('./Node');\n\nclass AuthNode extends Node {\n  constructor() {\n    super('authNode');\n  }\n\n  seria"
  },
  {
    "path": "packages/flowtest-electron/src/utils/flowparser/DelayNode.js",
    "chars": 482,
    "preview": "const { Node } = require('./Node');\n\nclass DelayNode extends Node {\n  constructor() {\n    super('delayNode');\n  }\n\n  ser"
  },
  {
    "path": "packages/flowtest-electron/src/utils/flowparser/NestedFlowNode.js",
    "chars": 491,
    "preview": "const { Node } = require('./Node');\n\nclass NestedFlowNode extends Node {\n  constructor() {\n    super('flowNode');\n  }\n\n "
  },
  {
    "path": "packages/flowtest-electron/src/utils/flowparser/Node.js",
    "chars": 312,
    "preview": "class Node {\n  constructor(type) {\n    this.type = type;\n  }\n\n  serialize(id, data, metadata) {\n    throw new Error('Ser"
  },
  {
    "path": "packages/flowtest-electron/src/utils/flowparser/OutputNode.js",
    "chars": 507,
    "preview": "const { Node } = require('./Node');\n\nclass OutputNode extends Node {\n  constructor() {\n    super('outputNode');\n  }\n\n  s"
  },
  {
    "path": "packages/flowtest-electron/src/utils/flowparser/RequestNode.js",
    "chars": 488,
    "preview": "const { Node } = require('./Node');\n\nclass RequestNode extends Node {\n  constructor() {\n    super('requestNode');\n  }\n\n "
  },
  {
    "path": "packages/flowtest-electron/src/utils/flowparser/SetVarNode.js",
    "chars": 485,
    "preview": "const { Node } = require('./Node');\n\nclass SetVarNode extends Node {\n  constructor() {\n    super('setVarNode');\n  }\n\n  s"
  },
  {
    "path": "packages/flowtest-electron/src/utils/flowparser/StartNode.js",
    "chars": 385,
    "preview": "const { Node } = require('./Node');\n\nclass StartNode extends Node {\n  constructor() {\n    super('startNode');\n  }\n\n  ser"
  },
  {
    "path": "packages/flowtest-electron/src/utils/flowparser/parser.js",
    "chars": 7834,
    "preview": "const { cloneDeep } = require('lodash');\nconst { AuthNode } = require('./AuthNode');\nconst { NestedFlowNode } = require("
  },
  {
    "path": "packages/flowtest-electron/src/utils/generate-request-body.js",
    "chars": 4178,
    "preview": "const generateRequestBodyExample = (schema, level = 0, context = { processedSchemas: new Set() }) => {\n  if (!schema) re"
  },
  {
    "path": "packages/flowtest-electron/src/utils/generate-request-parameters.js",
    "chars": 3017,
    "preview": "function generateParameterExample(parameter) {\n  if (!parameter.schema) return {};\n\n  const schema = parameter.schema;\n\n"
  },
  {
    "path": "packages/flowtest-electron/tests/store/collection-store.test.js",
    "chars": 2290,
    "preview": "const path = require('path');\n\nconst Collections = require('../../src/store/collection');\nconst createDirectory = requir"
  },
  {
    "path": "packages/flowtest-electron/tests/store/settings-store.test.js",
    "chars": 874,
    "preview": "const Settings = require('../../src/store/settings');\n\ndescribe('settings-store', () => {\n  it('should create and get se"
  },
  {
    "path": "packages/flowtest-electron/tests/test.yaml",
    "chars": 21604,
    "preview": "openapi: 3.0.3\ninfo:\n  title: Swagger Petstore - OpenAPI 3.0\n  description: |-\n    This is a sample Pet Store Server bas"
  },
  {
    "path": "packages/flowtest-electron/tests/utils/collection-parser.test.js",
    "chars": 3949,
    "preview": "const SwaggerParser = require('@apidevtools/swagger-parser');\nconst JsonRefs = require('json-refs');\nconst parseOpenAPIS"
  },
  {
    "path": "packages/flowtest-electron/tests/utils/filemanager.test.js",
    "chars": 2457,
    "preview": "const createDirectory = require('../../src/utils/filemanager/createdirectory');\nconst deleteDirectory = require('../../s"
  },
  {
    "path": "packages/flowtest-electron/tests/utils/flowtest-ai.test.js",
    "chars": 2742,
    "preview": "const fs = require('fs');\nconst SwaggerParser = require('@apidevtools/swagger-parser');\nconst JsonRefs = require('json-r"
  },
  {
    "path": "packages/flowtest-electron/tests/utils/flowtest-parser.test.js",
    "chars": 7003,
    "preview": "const { deserialize, serialize } = require('../../src/utils/flowparser/parser');\n\ndescribe('FlowTest parser', () => {\n  "
  },
  {
    "path": "packages/flowtest-electron/tests/watcher.test.js",
    "chars": 7700,
    "preview": "const Watcher = require('../src/app/watcher');\nconst path = require('path');\nconst createDirectory = require('../src/uti"
  },
  {
    "path": "pnpm-workspace.yaml",
    "chars": 67,
    "preview": "packages:\n  - packages/flowtest-cli\n  - packages/flowtest-electron\n"
  },
  {
    "path": "postcss.config.js",
    "chars": 83,
    "preview": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n};\n"
  },
  {
    "path": "public/index.html",
    "chars": 2044,
    "preview": "<!-- prettier-ignore -->\n<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"icon\" hre"
  },
  {
    "path": "public/manifest.json",
    "chars": 510,
    "preview": "{\n  \"short_name\": \"FlowTest\",\n  \"name\": \"Drag & Drop UI for openAI function calling\",\n  \"icons\": [\n    {\n      \"src\": \"f"
  },
  {
    "path": "public/robots.txt",
    "chars": 67,
    "preview": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "src/App.css",
    "chars": 564,
    "preview": ".App {\n  text-align: center;\n}\n\n.App-logo {\n  height: 40vmin;\n  pointer-events: none;\n}\n\n@media (prefers-reduced-motion:"
  },
  {
    "path": "src/App.js",
    "chars": 234,
    "preview": "import React from 'react';\nimport './App.css';\nimport Routes from './routes';\nimport { HashRouter } from 'react-router-d"
  },
  {
    "path": "src/App.test.js",
    "chars": 246,
    "preview": "import { render, screen } from '@testing-library/react';\nimport App from './App';\n\ntest('renders learn react link', () ="
  },
  {
    "path": "src/components/atoms/EditableTextItem.js",
    "chars": 577,
    "preview": "import React from 'react';\nimport { PropTypes } from 'prop-types';\nimport { EditText } from 'react-edit-text';\nimport 'r"
  },
  {
    "path": "src/components/atoms/Editor.js",
    "chars": 3021,
    "preview": "import React, { useRef, useEffect, useState } from 'react';\n\nimport { basicSetup } from 'codemirror';\nimport { EditorSta"
  },
  {
    "path": "src/components/atoms/Logo.js",
    "chars": 269,
    "preview": "import React from 'react';\nimport FlowTestAI from 'assets/icons/FlowTestAI.png';\n\nconst AppLogo = ({ styleClasses }) => "
  },
  {
    "path": "src/components/atoms/SelectAuthKeys.js",
    "chars": 2429,
    "preview": "// ToDo: Remove as we ar not using it\n// import React, { Fragment, useState } from 'react';\n// import { Listbox, Transit"
  },
  {
    "path": "src/components/atoms/SelectEnvironment.js",
    "chars": 2660,
    "preview": "import React, { Fragment, useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Listbox, Transition "
  },
  {
    "path": "src/components/atoms/Tabs.js",
    "chars": 3966,
    "preview": "import React, { useState } from 'react';\nimport { XMarkIcon } from '@heroicons/react/24/outline';\nimport { useTabStore }"
  },
  {
    "path": "src/components/atoms/ThemeController.js",
    "chars": 2693,
    "preview": "import React from 'react';\nimport Tippy from '@tippyjs/react';\nimport 'tippy.js/dist/tippy.css';\n\nconst ThemeController "
  },
  {
    "path": "src/components/atoms/common/Button.js",
    "chars": 3171,
    "preview": "import React from 'react';\nimport { PropTypes } from 'prop-types';\n\n// ToDo: can be more generalized\nconst Button = ({\n "
  },
  {
    "path": "src/components/atoms/common/HomeLoadingScreen.js",
    "chars": 577,
    "preview": "import React from 'react';\n// import PropTypes from 'prop-types';\nimport FlowGPU from 'assets/icons/Flow-GPU-text-no-bac"
  },
  {
    "path": "src/components/atoms/common/HorizontalDivider.js",
    "chars": 278,
    "preview": "import React from 'react';\n\nconst HorizontalDivider = ({ themeColor, themeStyles }) => {\n  return (\n    <div\n      class"
  },
  {
    "path": "src/components/atoms/common/LoadingSpinner.js",
    "chars": 339,
    "preview": "import React, { useState } from 'react';\n\nconst LoadingSpinner = ({ spinnerColor }) => {\n  return (\n    <div className='"
  },
  {
    "path": "src/components/atoms/common/NumberInput.js",
    "chars": 430,
    "preview": "import React from 'react';\n\nconst NumberInput = ({ placeHolder, onChangeHandler, name, value }) => {\n  return (\n    <inp"
  },
  {
    "path": "src/components/atoms/common/TextEditor.js",
    "chars": 5127,
    "preview": "import React, { useRef, useEffect, useState } from 'react';\n\nimport { basicSetup } from 'codemirror';\nimport { EditorSta"
  },
  {
    "path": "src/components/atoms/common/TextInput.js",
    "chars": 625,
    "preview": "import React from 'react';\n\nconst TextInput = ({ id, placeHolder, onChangeHandler, name, value, disableState }) => {\n  c"
  },
  {
    "path": "src/components/atoms/common/TextInputWithLabel.js",
    "chars": 668,
    "preview": "import React from 'react';\n\nconst TextInputWithLabel = ({ children, placeHolder, onChangeHandler, name, value, label }) "
  },
  {
    "path": "src/components/atoms/common/TimeoutSelector.js",
    "chars": 2918,
    "preview": "import React, { useState, Fragment } from 'react';\nimport { ClockIcon } from '@heroicons/react/24/outline';\nimport { Lis"
  },
  {
    "path": "src/components/atoms/flow/FlowNode.js",
    "chars": 1469,
    "preview": "import React from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Handle, Position } from 'reactflow';\n\nconst "
  },
  {
    "path": "src/components/atoms/flow/NodeHorizontalDivider.js",
    "chars": 176,
    "preview": "import React from 'react';\n\nconst NodeHorizontalDivider = () => {\n  return <div className='bg-background-dark h-[2px] w-"
  },
  {
    "path": "src/components/atoms/flow/Textarea.js",
    "chars": 461,
    "preview": "import React from 'react';\n\nconst Textarea = ({ id, placeHolder, onChangeHandler, name, value, rows }) => {\n  return (\n "
  },
  {
    "path": "src/components/atoms/sidebar/collections/OptionsMenu.js",
    "chars": 6121,
    "preview": "import React, { Fragment } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Menu, Transition } from '@head"
  },
  {
    "path": "src/components/atoms/sidebar/environments/EnvOptionsMenu.js",
    "chars": 2772,
    "preview": "import React, { Fragment } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Menu, Transition } from '@head"
  },
  {
    "path": "src/components/atoms/util.js",
    "chars": 3213,
    "preview": "import _, { isEqual, map } from 'lodash';\n\nexport const compare = (a, b) => {\n  const result = {\n    different: [],\n    "
  },
  {
    "path": "src/components/layouts/SplitPane.js",
    "chars": 1487,
    "preview": "import React from 'react';\nimport { Allotment } from 'allotment';\nimport 'allotment/dist/style.css';\nimport Workspace fr"
  },
  {
    "path": "src/components/layouts/WithoutSplitPane.js",
    "chars": 721,
    "preview": "import React from 'react';\nimport { Allotment } from 'allotment';\nimport 'allotment/dist/style.css';\nimport Workspace fr"
  },
  {
    "path": "src/components/molecules/environment/index.js",
    "chars": 5023,
    "preview": "import React, { useState } from 'react';\nimport useEnvStore from 'stores/EnvStore';\nimport 'react-edit-text/dist/index.c"
  },
  {
    "path": "src/components/molecules/flow/AddNodes.js",
    "chars": 17852,
    "preview": "import React, { useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Popover, Transition } from '@h"
  },
  {
    "path": "src/components/molecules/flow/constants/assertOperators.js",
    "chars": 182,
    "preview": "const AssertOperators = {\n  isLessThan: 'isLessThan',\n  isGreaterThan: 'isGreaterThan',\n  isEqualTo: 'isEqualTo',\n  isNo"
  },
  {
    "path": "src/components/molecules/flow/constants/evaluateOperators.js",
    "chars": 128,
    "preview": "const EvaluateOperators = {\n  Add: 'Add two numbers',\n  Subtract: 'Subtract two numbers',\n};\n\nexport default EvaluateOpe"
  },
  {
    "path": "src/components/molecules/flow/constants/requestNodes.js",
    "chars": 784,
    "preview": "const requestNodes = [\n  {\n    requestType: 'GET',\n    description: 'GET is used to request data from a specified resour"
  },
  {
    "path": "src/components/molecules/flow/edges/ButtonEdge.js",
    "chars": 1882,
    "preview": "import React from 'react';\nimport { BaseEdge, EdgeLabelRenderer, getSmoothStepPath } from 'reactflow';\nimport useCanvasS"
  },
  {
    "path": "src/components/molecules/flow/flowtestai.js",
    "chars": 5340,
    "preview": "import { GENAI_MODELS } from 'constants/Common';\nimport { addOrUpdateDotEnvironmentFile } from 'service/collection';\nimp"
  },
  {
    "path": "src/components/molecules/flow/graph/Graph.js",
    "chars": 8557,
    "preview": "// assumption is that apis are giving json as output\n\nimport { cloneDeep } from 'lodash';\nimport { readFlowTestSync } fr"
  },
  {
    "path": "src/components/molecules/flow/graph/GraphLogger.js",
    "chars": 362,
    "preview": "export const LogLevel = Object.freeze({\n  INFO: 0,\n  WARN: 1,\n  ERROR: 2,\n});\n\nclass GraphLogger {\n  constructor() {\n   "
  },
  {
    "path": "src/components/molecules/flow/graph/GraphRun.js",
    "chars": 1633,
    "preview": "import GraphLogger, { LogLevel } from './GraphLogger';\nimport Graph from './Graph';\nimport { useTabStore } from 'stores/"
  },
  {
    "path": "src/components/molecules/flow/graph/compute/assertnode.js",
    "chars": 1814,
    "preview": "import AssertOperators from '../../constants/assertOperators';\nimport { computeNodeVariable } from './utils';\nimport Nod"
  },
  {
    "path": "src/components/molecules/flow/graph/compute/authnode.js",
    "chars": 1413,
    "preview": "import { computeVariables } from './utils';\nimport Node from './node';\nimport { LogLevel } from '../GraphLogger';\n\nclass"
  },
  {
    "path": "src/components/molecules/flow/graph/compute/nestedflownode.js",
    "chars": 482,
    "preview": "import Graph1 from '../Graph';\nimport Node from './node';\n\nclass nestedFlowNode extends Node {\n  constructor(nodes, edge"
  },
  {
    "path": "src/components/molecules/flow/graph/compute/node.js",
    "chars": 179,
    "preview": "class Node {\n  constructor(type) {\n    this.type = type;\n  }\n\n  evaluate() {\n    throw new Error('Evaluate method must b"
  },
  {
    "path": "src/components/molecules/flow/graph/compute/requestnode.js",
    "chars": 4099,
    "preview": "import { cloneDeep } from 'lodash';\nimport { computeNodeVariables, computeVariables } from '../compute/utils';\nimport { "
  },
  {
    "path": "src/components/molecules/flow/graph/compute/setvarnode.js",
    "chars": 2540,
    "preview": "import { computeNodeVariable } from './utils';\nimport Node from './node';\nimport EvaluateOperators from '../../constants"
  },
  {
    "path": "src/components/molecules/flow/graph/compute/utils.js",
    "chars": 2547,
    "preview": "export const computeNodeVariable = (variable, prevNodeOutputData) => {\n  if (variable.type.toLowerCase() === 'string') {"
  },
  {
    "path": "src/components/molecules/flow/graph/compute/utils.test.js",
    "chars": 884,
    "preview": "const { computeVariables } = require('./utils');\n\ndescribe('Utils', () => {\n  it('should compute variables correctly', ("
  },
  {
    "path": "src/components/molecules/flow/index.js",
    "chars": 7886,
    "preview": "import React, { useCallback, useMemo, useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport ReactFlow,"
  },
  {
    "path": "src/components/molecules/flow/nodes/AssertNode.js",
    "chars": 6708,
    "preview": "import React, { useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Handle, Position } from 'react"
  },
  {
    "path": "src/components/molecules/flow/nodes/AuthNode.js",
    "chars": 7808,
    "preview": "import React, { useState, Fragment } from 'react';\nimport { PropTypes } from 'prop-types';\nimport FlowNode from 'compone"
  },
  {
    "path": "src/components/molecules/flow/nodes/DelayNode.js",
    "chars": 1012,
    "preview": "import * as React from 'react';\nimport { PropTypes } from 'prop-types';\nimport FlowNode from 'components/atoms/flow/Flow"
  },
  {
    "path": "src/components/molecules/flow/nodes/FormDataSelector.js",
    "chars": 2873,
    "preview": "import React, { useState, Fragment } from 'react';\nimport { ClockIcon } from '@heroicons/react/24/outline';\nimport { Lis"
  },
  {
    "path": "src/components/molecules/flow/nodes/NestedFlowNode.js",
    "chars": 3183,
    "preview": "import React, { useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport FlowNode from 'components/atoms/"
  },
  {
    "path": "src/components/molecules/flow/nodes/OutputNode.js",
    "chars": 1760,
    "preview": "import * as React from 'react';\nimport { PropTypes } from 'prop-types';\nimport FlowNode from 'components/atoms/flow/Flow"
  },
  {
    "path": "src/components/molecules/flow/nodes/RequestBody.js",
    "chars": 14027,
    "preview": "import React, { Fragment, useState, useRef } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Listbox, Tra"
  },
  {
    "path": "src/components/molecules/flow/nodes/RequestNode.js",
    "chars": 18240,
    "preview": "import React, { useEffect, useState, Fragment } from 'react';\nimport { PropTypes } from 'prop-types';\nimport RequestBody"
  },
  {
    "path": "src/components/molecules/flow/nodes/SetVarNode.js",
    "chars": 9683,
    "preview": "import React from 'react';\nimport { PropTypes } from 'prop-types';\nimport FlowNode from 'components/atoms/flow/FlowNode'"
  },
  {
    "path": "src/components/molecules/flow/utils.js",
    "chars": 3676,
    "preview": "import { isEqual, reduce, map } from 'lodash';\nimport requestNodes from './constants/requestNodes';\n\nexport const orderN"
  },
  {
    "path": "src/components/molecules/footers/MainFooter.js",
    "chars": 3157,
    "preview": "import React, { useState } from 'react';\nimport {\n  ArrowLeftEndOnRectangleIcon,\n  ArrowRightStartOnRectangleIcon,\n  Cog"
  },
  {
    "path": "src/components/molecules/headers/MainHeader.js",
    "chars": 1534,
    "preview": "import React from 'react';\nimport ThemeController from 'components/atoms/ThemeController';\nimport AppLogo from 'componen"
  },
  {
    "path": "src/components/molecules/headers/SideBarHeader.js",
    "chars": 1188,
    "preview": "import React from 'react';\nimport { UserIcon } from '@heroicons/react/20/solid';\nimport { ChevronRightIcon } from '@hero"
  },
  {
    "path": "src/components/molecules/headers/SideBarSubHeader.js",
    "chars": 1751,
    "preview": "import React, { useState } from 'react';\nimport { FolderArrowDownIcon } from '@heroicons/react/24/outline';\nimport { Plu"
  },
  {
    "path": "src/components/molecules/headers/TabPanelHeader.js",
    "chars": 6206,
    "preview": "import React, { useState } from 'react';\nimport { SparklesIcon, DocumentTextIcon } from '@heroicons/react/24/outline';\ni"
  },
  {
    "path": "src/components/molecules/headers/WorkspaceHeader.js",
    "chars": 1952,
    "preview": "import React, { useState } from 'react';\nimport { PlusIcon } from '@heroicons/react/20/solid';\nimport Tabs from '../../a"
  },
  {
    "path": "src/components/molecules/modals/AddEnvVariableModal.js",
    "chars": 5210,
    "preview": "import React, { Fragment, useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, Transition }"
  },
  {
    "path": "src/components/molecules/modals/ConfirmActionModal.js",
    "chars": 3014,
    "preview": "import React, { Fragment } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, Transition } from '@he"
  },
  {
    "path": "src/components/molecules/modals/EditEnvVariableModal.js",
    "chars": 3555,
    "preview": "import React, { Fragment, useState, useEffect } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, T"
  },
  {
    "path": "src/components/molecules/modals/GenAIUsageDisclaimer.js",
    "chars": 5612,
    "preview": "import React, { Fragment, useState, useEffect } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, T"
  },
  {
    "path": "src/components/molecules/modals/GenerateFlowTestModal.js",
    "chars": 32732,
    "preview": "import React, { Fragment, useState, useEffect } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, T"
  },
  {
    "path": "src/components/molecules/modals/ImportCollectionModal.js",
    "chars": 7002,
    "preview": "import React, { Fragment, useRef, useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, Tran"
  },
  {
    "path": "src/components/molecules/modals/OpenCollectionModal.js",
    "chars": 6992,
    "preview": "import React, { Fragment, useRef, useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, Tran"
  },
  {
    "path": "src/components/molecules/modals/OutputNodeExpandedModal.js",
    "chars": 2805,
    "preview": "import React, { Fragment, useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, Transition, "
  },
  {
    "path": "src/components/molecules/modals/SaveFlowModal.js",
    "chars": 2242,
    "preview": "import React from 'react';\nimport { PropTypes } from 'prop-types';\nimport { InboxArrowDownIcon } from '@heroicons/react/"
  },
  {
    "path": "src/components/molecules/modals/SettingsModal.js",
    "chars": 10364,
    "preview": "import React, { Fragment, useState, useEffect } from 'react';\nimport { Dialog, Transition, Tab } from '@headlessui/react"
  },
  {
    "path": "src/components/molecules/modals/create/NewCollectionModal.js",
    "chars": 2972,
    "preview": "import React, { Fragment } from 'react';\nimport { Dialog, Transition } from '@headlessui/react';\nimport Button from 'com"
  },
  {
    "path": "src/components/molecules/modals/flow/AddVariableModal.js",
    "chars": 4694,
    "preview": "import React, { useState, Fragment } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, Transition }"
  },
  {
    "path": "src/components/molecules/modals/flow/NewFlowTestModal.js",
    "chars": 15339,
    "preview": "import React, { Fragment, useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, Transition, "
  },
  {
    "path": "src/components/molecules/modals/sidebar/NewEnvironmentFileModal.js",
    "chars": 9544,
    "preview": "import React, { Fragment, useState } from 'react';\nimport { Dialog, Transition, Listbox } from '@headlessui/react';\nimpo"
  },
  {
    "path": "src/components/molecules/modals/sidebar/NewLabelModal.js",
    "chars": 8295,
    "preview": "import React, { Fragment, useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, Transition }"
  },
  {
    "path": "src/components/molecules/sideSheets/FlowLogs.js",
    "chars": 8801,
    "preview": "import React, { useState } from 'react';\nimport {\n  ShieldCheckIcon,\n  BarsArrowUpIcon,\n  ExclamationTriangleIcon,\n  XCi"
  },
  {
    "path": "src/components/molecules/sidebar/Empty.js",
    "chars": 1754,
    "preview": "import React, { useState } from 'react';\nimport { FolderArrowDownIcon } from '@heroicons/react/24/outline';\nimport { Plu"
  },
  {
    "path": "src/components/molecules/sidebar/content/Collection.js",
    "chars": 4908,
    "preview": "import React, { useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { ArchiveBoxIcon, FolderIcon, Do"
  },
  {
    "path": "src/components/molecules/sidebar/content/Collections.js",
    "chars": 4883,
    "preview": "import React, { useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { OBJ_TYPES } from 'constants/Co"
  },
  {
    "path": "src/components/molecules/sidebar/content/Environment.js",
    "chars": 4860,
    "preview": "import React, { useState } from 'react';\nimport PropTypes from 'prop-types';\nimport { ArchiveBoxIcon, DocumentIcon, Tras"
  },
  {
    "path": "src/components/molecules/sidebar/content/Environments.js",
    "chars": 2148,
    "preview": "import React, { useState } from 'react';\nimport PropTypes from 'prop-types';\nimport Environment from './Environment';\nim"
  },
  {
    "path": "src/components/molecules/sidebar/content/index.js",
    "chars": 947,
    "preview": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport useNavigationStore from 'stores/AppNavBarStore';\ni"
  },
  {
    "path": "src/components/molecules/workspace/EmptyWorkSpaceContent.js",
    "chars": 6515,
    "preview": "import React, { useState } from 'react';\nimport Tippy from '@tippyjs/react';\nimport { RectangleStackIcon, Square3Stack3D"
  },
  {
    "path": "src/components/molecules/workspace/WorkspaceContent.js",
    "chars": 1751,
    "preview": "import React from 'react';\nimport Flow, { init } from 'components/molecules/flow';\nimport { useTabStore } from 'stores/T"
  },
  {
    "path": "src/components/organisms/AppNavBar.js",
    "chars": 5376,
    "preview": "import React from 'react';\nimport { Square3Stack3DIcon, RectangleStackIcon, ClockIcon } from '@heroicons/react/20/solid'"
  },
  {
    "path": "src/components/organisms/SideBar.js",
    "chars": 774,
    "preview": "import React from 'react';\nimport useCollectionStore from 'stores/CollectionStore';\nimport SideBarHeader from 'component"
  },
  {
    "path": "src/components/organisms/workspace/Workspace.js",
    "chars": 466,
    "preview": "import React from 'react';\nimport WorkspaceHeader from 'components/molecules/headers/WorkspaceHeader';\nimport WorkspaceC"
  },
  {
    "path": "src/components/pages/Home.js",
    "chars": 1707,
    "preview": "import React from 'react';\nimport SplitPane from '../layouts/SplitPane';\nimport MainHeader from '../molecules/headers/Ma"
  },
  {
    "path": "src/constants/AppNavBar.js",
    "chars": 766,
    "preview": "export const AppNavBarItems = {\n  // default value\n  collections: {\n    displayValue: 'Collections',\n    value: 'COLLECT"
  },
  {
    "path": "src/constants/Common.js",
    "chars": 780,
    "preview": "export const FLOW_FILE_SUFFIX_REGEX = /^.+\\.flow$/gm; // regex to check the file extension of flow files\n\nexport const C"
  },
  {
    "path": "src/constants/ImportCollectionTypes.js",
    "chars": 136,
    "preview": "const ImportCollectionTypes = {\n  YAML: 'yaml',\n  OPEN_API: 'open_api',\n  POSTMAN: 'postman',\n};\n\nexport default ImportC"
  },
  {
    "path": "src/constants/ModalNames.js",
    "chars": 147,
    "preview": "export const ModalNames = {\n  IMPORT_COLLECTION_MODAL: 'import-collection-modal',\n  CREATE_NEW_COLLECTION_MODAL: 'create"
  },
  {
    "path": "src/constants/WorkspaceDirectory.js",
    "chars": 689,
    "preview": "export const DirectoryOptionsActions = {\n  addNewFolder: {\n    displayValue: 'New Folder',\n    value: 'new-folder',\n    "
  },
  {
    "path": "src/constants/sidebar/Environnments.js",
    "chars": 171,
    "preview": "export const OptionsMenuActions = {\n  addNewEnvironment: {\n    displayValue: 'New Environment',\n    value: 'new-environm"
  },
  {
    "path": "src/index.css",
    "chars": 3680,
    "preview": "@import 'tailwindcss/base';\n@import 'tailwindcss/components';\n@import 'tailwindcss/utilities';\n@import url('https://font"
  },
  {
    "path": "src/index.js",
    "chars": 536,
    "preview": "import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport './index.css';\nimport App from './App';\nimpor"
  },
  {
    "path": "src/ipc/collection.js",
    "chars": 2696,
    "preview": "import useCollectionStore from 'stores/CollectionStore';\nimport { useEffect } from 'react';\n\nconst registerMainEventHand"
  },
  {
    "path": "src/ipc/settings.js",
    "chars": 1086,
    "preview": "import { useEffect } from 'react';\nimport useSettingsStore from 'stores/SettingsStore';\n\nconst registerSettingsEventHand"
  },
  {
    "path": "src/reportWebVitals.js",
    "chars": 364,
    "preview": "const reportWebVitals = (onPerfEntry) => {\n  if (onPerfEntry && onPerfEntry instanceof Function) {\n    import('web-vital"
  },
  {
    "path": "src/routes/Main.js",
    "chars": 262,
    "preview": "import React from 'react';\nimport { Outlet } from 'react-router';\nimport Home from '../components/pages/Home';\n\nconst Ma"
  },
  {
    "path": "src/routes/index.js",
    "chars": 140,
    "preview": "import { useRoutes } from 'react-router';\nimport Main from 'routes/Main';\n\nexport default function Routes() {\n  return u"
  },
  {
    "path": "src/service/collection.js",
    "chars": 15133,
    "preview": "import useCollectionStore from '../stores/CollectionStore';\nimport { v4 as uuidv4 } from 'uuid';\nimport { findItemInColl"
  },
  {
    "path": "src/service/settings.js",
    "chars": 768,
    "preview": "export const addLogSyncConfig = (enabled, hostUrl, accessId, accessKey) => {\n  try {\n    const { ipcRenderer } = window;"
  },
  {
    "path": "src/setupTests.js",
    "chars": 241,
    "preview": "// jest-dom adds custom jest matchers for asserting on DOM nodes.\n// allows you to do things like:\n// expect(element).to"
  }
]

// ... and 17 more files (download for full content)

About this extraction

This page contains the full source code of the FlowTestAI/FlowTest GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 217 files (635.3 KB), approximately 150.5k tokens, and a symbol index with 211 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!