├── .eslintignore
├── .gitattributes
├── .github
└── workflows
│ ├── ci.yml
│ └── site.yml
├── .gitignore
├── .prettierrc
├── .spellbound
├── description.md
└── pinecone_cache.json
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── .vscodeignore
├── ARCHITECTURE.md
├── CHANGELOG.md
├── LICENSE.md
├── LOCAL_SETUP.md
├── README.md
├── apps
├── agentitive-site
│ ├── .gitignore
│ ├── README.md
│ ├── babel.config.js
│ ├── blog
│ │ ├── 2023-04-05-new-site
│ │ │ ├── docusaurus-plushie-banner.jpeg
│ │ │ └── index.md
│ │ └── authors.yml
│ ├── docs
│ │ ├── intro.md
│ │ └── local_dev.md
│ ├── docusaurus.config.js
│ ├── package-lock.json
│ ├── package.json
│ ├── sidebars.js
│ ├── src
│ │ ├── components
│ │ │ └── HomepageFeatures
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.module.css
│ │ ├── css
│ │ │ └── custom.css
│ │ ├── definitions.d.ts
│ │ └── pages
│ │ │ ├── index.module.css
│ │ │ ├── index.tsx
│ │ │ └── markdown-page.md
│ ├── static
│ │ ├── .nojekyll
│ │ └── img
│ │ │ ├── favicon.ico
│ │ │ └── logo.svg
│ └── tsconfig.json
├── spellbound-shared
│ ├── package.json
│ ├── src
│ │ ├── birpc
│ │ │ ├── birpc.ts
│ │ │ ├── extension.ts
│ │ │ ├── index.ts
│ │ │ └── webview.ts
│ │ ├── index.ts
│ │ └── types
│ │ │ ├── Message.ts
│ │ │ └── index.ts
│ └── tsconfig.json
├── spellbound-ui
│ ├── .gitignore
│ ├── .vscode
│ │ └── settings.json
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── logo192.png
│ │ ├── logo512.png
│ │ ├── manifest.json
│ │ └── robots.txt
│ ├── scripts
│ │ └── build-react-no-split.js
│ ├── src
│ │ ├── backup.ts
│ │ ├── components
│ │ │ ├── App
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.module.scss
│ │ │ ├── InputPanel
│ │ │ │ ├── InputPanel.tsx
│ │ │ │ ├── index.ts
│ │ │ │ └── styles.module.scss
│ │ │ ├── MessageHistory
│ │ │ │ ├── MessageHistory.tsx
│ │ │ │ ├── index.ts
│ │ │ │ └── styles.module.scss
│ │ │ ├── MessageItem
│ │ │ │ ├── MessageItem.tsx
│ │ │ │ ├── index.ts
│ │ │ │ └── styles.module.scss
│ │ │ ├── index.ts
│ │ │ └── styles.ts
│ │ ├── index.tsx
│ │ ├── react-app-env.d.ts
│ │ ├── store.ts
│ │ ├── styles.scss
│ │ └── utilities
│ │ │ ├── rpc.ts
│ │ │ └── vscode.ts
│ ├── tsconfig.json
│ └── webpack.config.js
└── spellbound
│ ├── .eslintignore
│ ├── .eslintrc.json
│ ├── .gitattributes
│ ├── .gitignore
│ ├── .spellbound
│ ├── description.md
│ └── pinecone_cache.json
│ ├── .vscode
│ └── launch.json
│ ├── assets
│ ├── icon.png
│ ├── prompt.md
│ └── wand-magic-sparkles-solid.svg
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ ├── agent
│ │ ├── AgentLogicHandler.ts
│ │ └── index.ts
│ ├── api
│ │ ├── openai.ts
│ │ └── pinecone.ts
│ ├── commands
│ │ ├── indexFolder.ts
│ │ └── indexWorkspace.ts
│ ├── environment
│ │ ├── getCurrentModel.ts
│ │ ├── getModelList.ts
│ │ ├── getOpenAIKey.ts
│ │ ├── getPineconeIndex.ts
│ │ └── getPineconeKey.ts
│ ├── extension.ts
│ ├── inference
│ │ └── InferenceHandler.ts
│ ├── prompts
│ │ └── fillPrompt.ts
│ ├── tools
│ │ ├── AnyToolInterface.ts
│ │ ├── ToolEngine.ts
│ │ ├── ask
│ │ │ ├── AskToolInterface.ts
│ │ │ └── askToolImpl.ts
│ │ ├── cat
│ │ │ ├── CatToolInterface.ts
│ │ │ └── catToolImpl.ts
│ │ ├── diff
│ │ │ ├── DiffToolInterface.ts
│ │ │ └── diffToolImpl.ts
│ │ ├── done
│ │ │ ├── DoneToolInterface.ts
│ │ │ └── doneToolImpl.ts
│ │ ├── git
│ │ │ ├── GitToolInterface.ts
│ │ │ ├── gitToolImpl.ts
│ │ │ └── utility
│ │ │ │ └── runGitCommand.ts
│ │ ├── grep
│ │ │ ├── GrepToolInterface.ts
│ │ │ └── grepToolImpl.ts
│ │ ├── ls
│ │ │ ├── LsToolInterface.ts
│ │ │ └── lsToolImpl.ts
│ │ ├── move
│ │ │ ├── MoveToolInterface.ts
│ │ │ └── moveToolImpl.ts
│ │ ├── npm
│ │ │ ├── NpmToolInterface.ts
│ │ │ ├── npmToolImpl.ts
│ │ │ └── utility
│ │ │ │ └── runNpmCommand.ts
│ │ ├── search
│ │ │ ├── SearchToolInterface.ts
│ │ │ └── searchToolImpl.ts
│ │ ├── stats
│ │ │ ├── StatsToolInterface.ts
│ │ │ └── statsToolImpl.ts
│ │ └── write
│ │ │ ├── WriteToolInterface.ts
│ │ │ └── writeToolImpl.ts
│ ├── utils
│ │ ├── chunking.ts
│ │ ├── getCurrentWorkspaceFolder.ts
│ │ ├── paths.ts
│ │ └── uuid.ts
│ └── views
│ │ └── ChatboxViewProvider.ts
│ ├── tsconfig.json
│ └── webpack.config.js
├── assets
└── logo.png
├── common
├── config
│ └── rush
│ │ ├── .npmrc
│ │ ├── .npmrc-publish
│ │ ├── .pnpmfile.cjs
│ │ ├── artifactory.json
│ │ ├── build-cache.json
│ │ ├── command-line.json
│ │ ├── common-versions.json
│ │ ├── experiments.json
│ │ ├── pnpm-config.json
│ │ ├── pnpm-lock.yaml
│ │ ├── repo-state.json
│ │ ├── rush-plugins.json
│ │ └── version-policies.json
├── git-hooks
│ └── commit-msg.sample
└── scripts
│ ├── install-run-rush-pnpm.js
│ ├── install-run-rush.js
│ ├── install-run-rushx.js
│ └── install-run.js
├── rigs
├── eslint-plugin-local
│ ├── README.md
│ ├── lib
│ │ └── index.js
│ └── package.json
├── node-rig
│ ├── package.json
│ └── profiles
│ │ └── default
│ │ └── tsconfig-base.json
└── repo-scripts
│ ├── package.json
│ ├── src
│ ├── audit.ts
│ ├── bumpVersions.ts
│ ├── generateSnippets.ts
│ ├── publishLibrary.ts
│ ├── publishLibs.ts
│ ├── utils
│ │ ├── index.ts
│ │ ├── npm
│ │ │ ├── PackageMetadata.ts
│ │ │ ├── getRemotePackageDependencies.ts
│ │ │ ├── getRemotePackageMetadata.ts
│ │ │ ├── getRemotePackageVersion.ts
│ │ │ └── index.ts
│ │ └── reflect
│ │ │ ├── allApps.ts
│ │ │ ├── allLibraries.ts
│ │ │ ├── allProjects.ts
│ │ │ └── index.ts
│ └── versions.ts
│ ├── templates
│ └── tsconfig.json
│ └── tsconfig.json
└── rush.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/*.md
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Don't allow people to merge changes to these generated files, because the result
2 | # may be invalid. You need to run "rush update" again.
3 | pnpm-lock.yaml merge=text
4 | shrinkwrap.yaml merge=binary
5 | npm-shrinkwrap.json merge=binary
6 | yarn.lock merge=binary
7 |
8 | # Rush's JSON config files use JavaScript-style code comments. The rule below prevents pedantic
9 | # syntax highlighters such as GitHub's from highlighting these comments as errors. Your text editor
10 | # may also require a special configuration to allow comments in JSON.
11 | #
12 | # For more information, see this issue: https://github.com/microsoft/rushstack/issues/1088
13 | #
14 | *.json linguist-language=JSON-with-Comments
15 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | push:
4 | branches: [ "main" ]
5 | pull_request:
6 | branches: [ "main" ]
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v3
12 | with:
13 | fetch-depth: 2
14 | - uses: actions/setup-node@v3
15 | with:
16 | node-version: 16
17 | - name: Verify Change Logs
18 | run: node common/scripts/install-run-rush.js change --verify
19 | - name: Rush Install
20 | run: node common/scripts/install-run-rush.js install
21 | - name: Rush rebuild
22 | run: node common/scripts/install-run-rush.js rebuild --verbose
23 |
--------------------------------------------------------------------------------
/.github/workflows/site.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to GitHub Pages
2 |
3 | permissions: write-all
4 |
5 | on:
6 | push:
7 | branches:
8 | - main
9 | # Review gh actions docs if you want to further define triggers, paths, etc
10 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on
11 |
12 | jobs:
13 | deploy:
14 | name: Deploy to GitHub Pages
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v3
18 | - uses: actions/setup-node@v3
19 | with:
20 | node-version: 18
21 |
22 | - name: Rush Install
23 | run: node common/scripts/install-run-rush.js install
24 | - name: Build website
25 | run: node common/scripts/install-run-rush.js build --to agentitive-site
26 |
27 | # Popular action to deploy to GitHub Pages:
28 | # Docs: https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-docusaurus
29 | - name: Deploy to GitHub Pages
30 | uses: peaceiris/actions-gh-pages@v3
31 | with:
32 | github_token: ${{ secrets.GITHUB_TOKEN }}
33 | # Build output to publish to the `gh-pages` branch:
34 | publish_dir: ./apps/agentitive-site/build
35 | # The following lines assign commit authorship to the official
36 | # GH-Actions bot for deploys to `gh-pages` branch:
37 | # https://github.com/actions/checkout/issues/13#issuecomment-724415212
38 | # The GH actions bot is used by default if you didn't specify the two fields.
39 | # You can swap them out with your own user credentials.
40 | user_name: github-actions[bot]
41 | user_email: 41898282+github-actions[bot]@users.noreply.github.com
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | *.log
3 | npm-debug.log*
4 | yarn-debug.log*
5 | yarn-error.log*
6 |
7 | # Runtime data
8 | *.pid
9 | *.seed
10 | *.pid.lock
11 |
12 | # Directory for instrumented libs generated by jscoverage/JSCover
13 | lib-cov
14 |
15 | # Coverage directory used by tools like istanbul
16 | coverage
17 |
18 | # nyc test coverage
19 | .nyc_output
20 |
21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
22 | .grunt
23 |
24 | # Bower dependency directory (https://bower.io/)
25 | bower_components
26 |
27 | # node-waf configuration
28 | .lock-wscript
29 |
30 | # Compiled binary addons (https://nodejs.org/api/addons.html)
31 | build/Release
32 |
33 | # Dependency directories
34 | node_modules/
35 | jspm_packages/
36 |
37 | # Optional npm cache directory
38 | .npm
39 |
40 | # Optional eslint cache
41 | .eslintcache
42 |
43 | # Optional REPL history
44 | .node_repl_history
45 |
46 | # Output of 'npm pack'
47 | *.tgz
48 |
49 | # Yarn Integrity file
50 | .yarn-integrity
51 |
52 | # dotenv environment variables file
53 | .env
54 |
55 | # next.js build output
56 | .next
57 |
58 | # OS X temporary files
59 | .DS_Store
60 |
61 | # IntelliJ IDEA project files; if you want to commit IntelliJ settings, this recipe may be helpful:
62 | # https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
63 | .idea/
64 | *.iml
65 |
66 | # Rush temporary files
67 | common/deploy/
68 | common/temp/
69 | common/autoinstallers/*/.npmrc
70 | **/.rush/temp/
71 |
72 | # Heft temporary files
73 | .heft
74 | dist
75 | apps/spellbound/static
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": false
4 | }
5 |
--------------------------------------------------------------------------------
/.spellbound/description.md:
--------------------------------------------------------------------------------
1 | Spellbound is a VS Code extension that interacts with the OpenAI API to provide an intelligent coding assistant. Spellbound uses an internal conversational dialogue, wherein the underlying LLM agent uses 'tools' to interact with, understand, and perform tasks on your codebase.
2 |
--------------------------------------------------------------------------------
/.spellbound/pinecone_cache.json:
--------------------------------------------------------------------------------
1 | {
2 | ".vscode": ""
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // See http://go.microsoft.com/fwlink/?LinkId=827846
3 | // for the documentation about the extensions.json format
4 | "recommendations": ["dbaeumer.vscode-eslint", "amodio.tsl-problem-matcher"]
5 | }
6 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | {
6 | "version": "0.2.0",
7 | "configurations": [
8 | {
9 | "name": "Launch Extension",
10 | "type": "extensionHost",
11 | "request": "launch",
12 | "cwd": "${workspaceFolder}/apps/spellbound/",
13 | "args": [
14 | "--extensionDevelopmentPath=${workspaceFolder}/apps/spellbound/"
15 | ],
16 | "outFiles": [
17 | "${workspaceFolder}/apps/spellbound/dist/**/*.js"
18 | ],
19 | // run rushx build in apps/examples/flow-editor first
20 | // "preLaunchTask": "rush build"
21 | },
22 | {
23 | "type": "chrome",
24 | "request": "attach",
25 | "name": "Attach WebView",
26 | "port": 9222,
27 | "sourceMaps": true,
28 | "webRoot": "${workspaceFolder}"
29 | }
30 | ]
31 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "out": false, // set this to true to hide the "out" folder with the compiled JS files
5 | "dist": false // set this to true to hide the "dist" folder with the compiled JS files
6 | },
7 | "search.exclude": {
8 | "out": true, // set this to false to include "out" folder in search results
9 | "dist": true // set this to false to include "dist" folder in search results
10 | },
11 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
12 | "typescript.tsc.autoDetect": "off",
13 | "cSpell.words": [
14 | "Birpc"
15 | ]
16 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // See https://go.microsoft.com/fwlink/?LinkId=733558
2 | // for the documentation about the tasks.json format
3 | {
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "type": "npm",
8 | "script": "watch",
9 | "problemMatcher": "$ts-webpack-watch",
10 | "isBackground": true,
11 | "presentation": {
12 | "reveal": "never",
13 | "group": "watchers"
14 | },
15 | "group": {
16 | "kind": "build",
17 | "isDefault": true
18 | }
19 | },
20 | {
21 | "type": "npm",
22 | "script": "watch-tests",
23 | "problemMatcher": "$tsc-watch",
24 | "isBackground": true,
25 | "presentation": {
26 | "reveal": "never",
27 | "group": "watchers"
28 | },
29 | "group": "build"
30 | },
31 | {
32 | "label": "tasks: watch-tests",
33 | "dependsOn": [
34 | "npm: watch",
35 | "npm: watch-tests"
36 | ],
37 | "problemMatcher": []
38 | },
39 | {
40 | "type": "shell",
41 | "command": "rush build && rush generate-commands",
42 | "label": "rush build",
43 | "problemMatcher": [],
44 | },
45 | {
46 | "type": "npm",
47 | "script": "start",
48 | "label": "site",
49 | "options": {
50 | "cwd": "${workspaceFolder}/apps/agentitive-site"
51 | }
52 | }
53 | ]
54 | }
55 |
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | out/**
4 | node_modules/**
5 | src/**
6 | .gitignore
7 | .yarnrc
8 | webpack.config.js
9 | vsc-extension-quickstart.md
10 | **/tsconfig.json
11 | **/.eslintrc.json
12 | **/*.map
13 | **/*.ts
14 |
--------------------------------------------------------------------------------
/ARCHITECTURE.md:
--------------------------------------------------------------------------------
1 | # Spellbound Architecture Overview
2 |
3 | The Spellbound codebase can be categorized into multiple parts:
4 |
5 | 1. `apps/spellbound`: This is the main extension folder containing the source code for the Spellbound VS Code extension.
6 | - `src`: Contains the source code of the extension, including commands, tools, and APIs.
7 | - `assets`: Contains the assets used by the extension, such as images and sample markdown files.
8 |
9 | 2. `apps/spellbound-ui`: This folder contains the React-based user interface used in the extension's chatbox.
10 | - `src`: Contains the source code for the UI, including components, utilities, styles, and Redux store.
11 |
12 | 3. `apps/spellbound-shared`: This folder contains shared TypeScript code used by both the extension and the UI.
13 | - `src`: Contains the shared code, such as TypeScript type definitions and the BiDirectional Reactive Procedural Script (biRPC) library for communication between webviews and extensions.
14 |
15 | 4. `apps/agentitive-site`: This folder contains the source code for the documentation site generated with Docusaurus.
16 | - `docs`: Contains markdown files for the documentation.
17 | - `src`: Contains custom components and CSS for the documentation site.
18 |
19 | 5. Other folders and configuration files:
20 | - `.vscode`: Configurations for the workspace.
21 | - `rigs`: Contains various configurations and scripts for the repository.
22 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to the "spellbound" extension will be documented in this file.
4 |
5 | ## 0.0.3 - 2023-04-04
6 |
7 | ### Added
8 |
9 | - Add `search` tool functionality using Pinecone search engine.
10 | - Add related `stats` tool to list Pinecone index metadata.
11 | - Add related VS Code extension commands for workspace indexing.
12 |
13 | ### Fixed
14 |
15 | - Fix CSS for markdown `code` blocks in output.
16 |
17 | ## 0.0.2 - 2023-04-02
18 |
19 | ### Fixed
20 |
21 | - Fix documentation image links in extension marketplace.
22 |
23 | ## 0.0.1 - 2023-04-02
24 |
25 | - Initial release
26 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Michael Poteat
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LOCAL_SETUP.md:
--------------------------------------------------------------------------------
1 | # Local Setup
2 |
3 | This document provides instructions for setting up the local development environment for the Spellbound VS Code extension.
4 |
5 | ## Prerequisites
6 |
7 | - Node.js (v19.x.x recommended)
8 | - Visual Studio Code (v1.77.0 or later)
9 |
10 | ## Rush Monorepo
11 |
12 | The Spellbound repository is a [Rush](https://rushjs.io/) monorepo. This means that the repository contains multiple projects, each of which is a separate Node.js package. Rush uses [pnpm](https://pnpm.io/) under the hood.
13 |
14 | Installing Rush:
15 |
16 | ```
17 | npm install -g @microsoft/rush pnpm
18 | ```
19 |
20 | ## Steps to set up the local development environment
21 |
22 | 1. Clone the repository using `git`:
23 |
24 | ```
25 | git clone https://github.com/agentitive/spellbound.git
26 | ```
27 |
28 | 2. Change into the cloned directory:
29 |
30 | ```
31 | cd spellbound
32 | ```
33 |
34 | 3. Install dependencies:
35 |
36 | ```
37 | rush update
38 | ```
39 |
40 | 4. Compile the projects:
41 |
42 | ```
43 | rush watch
44 | ```
45 |
46 | 5. Open the project folder in Visual Studio Code:
47 |
48 | ```
49 | code .
50 | ```
51 |
52 | 6. Press `F5` to launch the extension in a new VS Code window.
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Autonomous and goal-seeking coding agents
10 |
11 |
12 | ---
13 |
14 | **Spellbound** is a [Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=mpoteat-vsce.spellbound) that leverages the power of the OpenAI API to create an intelligent coding assistant. With _Spellbound_, you can harness the capabilities of cutting-edge AI to improve your coding experience and productivity.
15 |
16 | ## Installation
17 |
18 | 1. Install the Spellbound VS Code extension from the marketplace.
19 | 2. Obtain an API key for the OpenAI API and configure it in your VS Code settings.
20 | 3. Obtain a Pinecone API key, create a Pinecone index, and configure it in your VS Code settings.
21 |
22 | ## Usage
23 |
24 | To use Spellbound, simply interact with your codebase as usual. Whenever you need help, type your request in the Spellbound dialog. Spellbound will use the OpenAI API to help you generate code, troubleshoot, and offer suggestions.
25 |
26 | Spellbound uses a "thought/action" control loop to create an autonomous coding agent, as described in the **[ReAct paper](https://arxiv.org/abs/2210.03629)**. The agent is able to take actions based on its current thoughts, and its future thoughts are influenced by the results of its actions.
27 |
28 | ## Extension Settings
29 |
30 | | Parameter | Description | Default | Scope |
31 | | --------------------------- | ---------------------------------------------------- | ------- | ------- |
32 | | spellbound.model | Select the AI model used by the Spellbound extension | gpt-4 | |
33 | | spellbound.openai_api_key | Enter your OpenAI API key here, used for inference. | | machine |
34 | | spellbound.pinecone_api_key | Enter your Pinecone API key here, used for search. | | machine |
35 | | spellbound.pinecone_index | Enter your Pinecone index name here, used for search | | machine |
36 |
37 | ### Configuring Settings
38 |
39 | To configure the extension settings, open the Settings editor in Visual Studio Code by clicking on the gear icon in the lower left corner and selecting "Settings" from the menu. In the search bar, type "Spellbound" to filter the settings related to the extension. You can then enter your API keys and select the preferred model from the available options.
40 |
41 | Environment variables `OPENAI_API_KEY` and `PINECONE_API_KEY` will be used in place of the settings if they are set.
42 |
43 | ## Tools
44 |
45 | The following tools are available for use in Spellbound. The underlying AI model uses tools to solve open-ended tasks.
46 |
47 | | Tool | Description |
48 | | ------------------------ | -------------------------------------------------------------------------------------- |
49 | | cat {path} | Read the content of a file at the given path. |
50 | | ls {path, recursive?} | List files and folders at the given path. |
51 | | search {description} | Search for a file or relevant information by description, via vector embedding search. |
52 | | write {path, contents} | Write (or overwrite) the given contents into the specified file. |
53 | | replace {path, old, new} | Replace all occurrences of `old` with `new` in the specified file. |
54 | | ask {question} | Ask a question to the user. |
55 | | npm {script} | Run an npm script (e.g., `npm run [script]`). |
56 | | done {output?} | Indicate that you are done with the task. |
57 |
58 | ## Features
59 |
60 | - Code generation based on natural language input
61 | - Troubleshooting and bug fixing assistance
62 | - Documentation writing and code critique
63 | - Autonomous and goal-seeking coding agents
64 | - Integration with your existing VS Code workflow
65 |
66 | ## Local Development
67 |
68 | To set up the local development environment for the Spellbound extension, see the [LOCAL_SETUP.md](LOCAL_SETUP.md) file.
69 |
70 | ## License
71 |
72 | Spellbound is released under the MIT License. See the LICENSE file for the full license text.
73 |
--------------------------------------------------------------------------------
/apps/agentitive-site/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | /node_modules
3 |
4 | # Production
5 | /build
6 |
7 | # Generated files
8 | .docusaurus
9 | .cache-loader
10 |
11 | # Misc
12 | .DS_Store
13 | .env.local
14 | .env.development.local
15 | .env.test.local
16 | .env.production.local
17 |
18 | npm-debug.log*
19 | yarn-debug.log*
20 | yarn-error.log*
21 |
--------------------------------------------------------------------------------
/apps/agentitive-site/README.md:
--------------------------------------------------------------------------------
1 | # Website
2 |
3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.
4 |
5 | ### Installation
6 |
7 | ```
8 | $ yarn
9 | ```
10 |
11 | ### Local Development
12 |
13 | ```
14 | $ yarn start
15 | ```
16 |
17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
18 |
19 | ### Build
20 |
21 | ```
22 | $ yarn build
23 | ```
24 |
25 | This command generates static content into the `build` directory and can be served using any static contents hosting service.
26 |
27 | ### Deployment
28 |
29 | Using SSH:
30 |
31 | ```
32 | $ USE_SSH=true yarn deploy
33 | ```
34 |
35 | Not using SSH:
36 |
37 | ```
38 | $ GIT_USER= yarn deploy
39 | ```
40 |
41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
42 |
--------------------------------------------------------------------------------
/apps/agentitive-site/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
3 | };
4 |
--------------------------------------------------------------------------------
/apps/agentitive-site/blog/2023-04-05-new-site/docusaurus-plushie-banner.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agentitive/spellbound/99c6e70f2aa74322d074d8b88c0b1cdef2d7acdf/apps/agentitive-site/blog/2023-04-05-new-site/docusaurus-plushie-banner.jpeg
--------------------------------------------------------------------------------
/apps/agentitive-site/blog/2023-04-05-new-site/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | slug: new-site
3 | title: New site!
4 | authors: [dustin]
5 | tags: [news]
6 | ---
7 |
8 | Hey, we got a new site!
9 |
--------------------------------------------------------------------------------
/apps/agentitive-site/blog/authors.yml:
--------------------------------------------------------------------------------
1 | dustin:
2 | name: Dustin
3 | title: Maintainer
4 | url: https://github.com/dustinlacewell
5 | image_url: https://avatars.githubusercontent.com/u/53952?v=4
6 |
--------------------------------------------------------------------------------
/apps/agentitive-site/docs/intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Spellbound
6 |
7 | **Spellbound** is a [Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=mpoteat-vsce.spellbound) that leverages the power of the OpenAI API to create an intelligent coding assistant. With _Spellbound_, you can harness the capabilities of cutting-edge AI to improve your coding experience and productivity.
8 |
9 | ## Installation
10 |
11 | 1. Install the Spellbound VS Code extension from the marketplace.
12 | 2. Obtain an API key for the OpenAI API and configure it in your VS Code settings.
13 | 3. Obtain a Pinecone API key, create a Pinecone index, and configure it in your VS Code settings.
14 |
15 | ## Usage
16 |
17 | To use Spellbound, simply interact with your codebase as usual. Whenever you need help, type your request in the Spellbound dialog. Spellbound will use the OpenAI API to help you generate code, troubleshoot, and offer suggestions.
18 |
19 | Spellbound uses a "thought/action" control loop to create an autonomous coding agent, as described in the **[ReAct paper](https://arxiv.org/abs/2210.03629)**. The agent is able to take actions based on its current thoughts, and its future thoughts are influenced by the results of its actions.
20 |
21 | ## Extension Settings
22 |
23 | | Parameter | Description | Default | Scope |
24 | | --------------------------- | ---------------------------------------------------- | ------- | ------- |
25 | | spellbound.model | Select the AI model used by the Spellbound extension | gpt-4 | |
26 | | spellbound.openai_api_key | Enter your OpenAI API key here, used for inference. | | machine |
27 | | spellbound.pinecone_api_key | Enter your Pinecone API key here, used for search. | | machine |
28 | | spellbound.pinecone_index | Enter your Pinecone index name here, used for search | | machine |
29 |
30 | ### Configuring Settings
31 |
32 | To configure the extension settings, open the Settings editor in Visual Studio Code by clicking on the gear icon in the lower left corner and selecting "Settings" from the menu. In the search bar, type "Spellbound" to filter the settings related to the extension. You can then enter your API keys and select the preferred model from the available options.
33 |
34 | Environment variables `OPENAI_API_KEY` and `PINECONE_API_KEY` will be used in place of the settings if they are set.
35 |
36 | ## Tools
37 |
38 | The following tools are available for use in Spellbound. The underlying AI model uses tools to solve open-ended tasks.
39 |
40 | | Tool | Description |
41 | | ------------------------ | -------------------------------------------------------------------------------------- |
42 | | cat {path} | Read the content of a file at the given path. |
43 | | ls {path, recursive?} | List files and folders at the given path. |
44 | | search {description} | Search for a file or relevant information by description, via vector embedding search. |
45 | | write {path, contents} | Write (or overwrite) the given contents into the specified file. |
46 | | replace {path, old, new} | Replace all occurrences of `old` with `new` in the specified file. |
47 | | ask {question} | Ask a question to the user. |
48 | | npm {script} | Run an npm script (e.g., `npm run [script]`). |
49 | | done {output?} | Indicate that you are done with the task. |
50 |
51 | ## Features
52 |
53 | - Code generation based on natural language input
54 | - Troubleshooting and bug fixing assistance
55 | - Documentation writing and code critique
56 | - Autonomous and goal-seeking coding agents
57 | - Integration with your existing VS Code workflow
58 |
59 | ## Local Development
60 |
61 | To set up the local development environment for the Spellbound extension, see the [local development docs](local_dev.md) file.
62 |
63 | ## License
64 |
65 | Spellbound is released under the MIT License. See the LICENSE file for the full license text.
66 |
--------------------------------------------------------------------------------
/apps/agentitive-site/docs/local_dev.md:
--------------------------------------------------------------------------------
1 | # Local Setup
2 |
3 | This document provides instructions for setting up the local development environment for the Spellbound VS Code extension.
4 |
5 | ## Prerequisites
6 |
7 | - Node.js (v19.x.x recommended)
8 | - Visual Studio Code (v1.77.0 or later)
9 |
10 | ## Rush Monorepo
11 |
12 | The Spellbound repository is a [Rush](https://rushjs.io/) monorepo. This means that the repository contains multiple projects, each of which is a separate Node.js package. Rush uses [pnpm](https://pnpm.io/) under the hood.
13 |
14 | Installing Rush:
15 |
16 | ```
17 | npm install -g @microsoft/rush pnpm
18 | ```
19 |
20 | ## Steps to set up the local development environment
21 |
22 | 1. Clone the repository using `git`:
23 |
24 | ```
25 | git clone https://github.com/agentitive/spellbound.git
26 | ```
27 |
28 | 2. Change into the cloned directory:
29 |
30 | ```
31 | cd spellbound
32 | ```
33 |
34 | 3. Install dependencies:
35 |
36 | ```
37 | rush update
38 | ```
39 |
40 | 4. Compile the projects:
41 |
42 | ```
43 | rush watch
44 | ```
45 |
46 | 5. Open the project folder in Visual Studio Code:
47 |
48 | ```
49 | code .
50 | ```
51 |
52 | 6. Press `F5` to launch the extension in a new VS Code window.
53 |
--------------------------------------------------------------------------------
/apps/agentitive-site/docusaurus.config.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | // Note: type annotations allow type checking and IDEs autocompletion
3 |
4 | const lightCodeTheme = require('prism-react-renderer/themes/github');
5 | const darkCodeTheme = require('prism-react-renderer/themes/dracula');
6 |
7 | /** @type {import('@docusaurus/types').Config} */
8 | const config = {
9 | title: 'agentitive.io',
10 | tagline: 'Autonomous goal-seeking agents',
11 | favicon: 'img/favicon.ico',
12 |
13 | // Set the production url of your site here
14 | url: 'https://agentitive.io',
15 | // Set the // pathname under which your site is served
16 | // For GitHub pages deployment, it is often '//'
17 | baseUrl: '/',
18 | trailingSlash: false,
19 |
20 | // GitHub pages deployment config.
21 | // If you aren't using GitHub pages, you don't need these.
22 | organizationName: 'agentitive', // Usually your GitHub org/user name.
23 | projectName: 'spellbound', // Usually your repo name.
24 |
25 | onBrokenLinks: 'throw',
26 | onBrokenMarkdownLinks: 'warn',
27 |
28 | // Even if you don't use internalization, you can use this field to set useful
29 | // metadata like html lang. For example, if your site is Chinese, you may want
30 | // to replace "en" with "zh-Hans".
31 | i18n: {
32 | defaultLocale: 'en',
33 | locales: ['en'],
34 | },
35 |
36 | presets: [
37 | [
38 | 'classic',
39 | /** @type {import('@docusaurus/preset-classic').Options} */
40 | ({
41 | docs: {
42 | sidebarPath: require.resolve('./sidebars.js'),
43 | },
44 | blog: {
45 | showReadingTime: true,
46 | },
47 | theme: {
48 | customCss: require.resolve('./src/css/custom.css'),
49 | },
50 | }),
51 | ],
52 | ],
53 |
54 | themeConfig:
55 | /** @type {import('@docusaurus/preset-classic').ThemeConfig} */
56 | ({
57 | // Replace with your project's social card
58 | image: 'img/docusaurus-social-card.jpg',
59 | navbar: {
60 | title: 'Agentitive.io',
61 | logo: {
62 | alt: 'My Site Logo',
63 | src: 'img/logo.svg',
64 | },
65 | items: [
66 | {
67 | type: 'docSidebar',
68 | sidebarId: 'docs',
69 | position: 'left',
70 | label: 'Docs',
71 | },
72 | {to: '/blog', label: 'Blog', position: 'right'},
73 | {
74 | href: 'https://github.com/agentitive/spellbound',
75 | label: 'GitHub',
76 | position: 'right',
77 | },
78 | ],
79 | },
80 | footer: {
81 | links: [
82 | {
83 | title: 'Docs',
84 | items: [
85 | {
86 | label: 'Tutorial',
87 | to: '/docs/intro',
88 | },
89 | ],
90 | },
91 | {
92 | title: 'Community',
93 | items: [
94 | {
95 | label: 'Discord',
96 | href: 'https://discord.gg/rZzrtJtrEP',
97 | }
98 | ],
99 | },
100 | {
101 | title: 'More',
102 | items: [
103 | {
104 | label: 'Blog',
105 | to: '/blog',
106 | },
107 | {
108 | label: 'GitHub',
109 | href: 'https://github.com/agentitive/spellbound',
110 | },
111 | ],
112 | },
113 | ],
114 | },
115 | prism: {
116 | theme: lightCodeTheme,
117 | darkTheme: darkCodeTheme,
118 | },
119 | }),
120 | };
121 |
122 | module.exports = config;
123 |
--------------------------------------------------------------------------------
/apps/agentitive-site/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "agentitive-site",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "docusaurus": "docusaurus",
7 | "start": "docusaurus start",
8 | "build": "docusaurus build",
9 | "swizzle": "docusaurus swizzle",
10 | "deploy": "docusaurus deploy",
11 | "clear": "docusaurus clear",
12 | "serve": "docusaurus serve",
13 | "write-translations": "docusaurus write-translations",
14 | "write-heading-ids": "docusaurus write-heading-ids",
15 | "typecheck": "tsc"
16 | },
17 | "dependencies": {
18 | "@docusaurus/core": "2.4.0",
19 | "@docusaurus/preset-classic": "2.4.0",
20 | "@mdx-js/react": "^1.6.22",
21 | "clsx": "^1.2.1",
22 | "prism-react-renderer": "^1.3.5",
23 | "react": "^17.0.2",
24 | "react-dom": "^17.0.2"
25 | },
26 | "devDependencies": {
27 | "@docusaurus/module-type-aliases": "2.4.0",
28 | "@tsconfig/docusaurus": "^1.0.5",
29 | "typescript": "~5.0.3"
30 | },
31 | "browserslist": {
32 | "production": [
33 | ">0.5%",
34 | "not dead",
35 | "not op_mini all"
36 | ],
37 | "development": [
38 | "last 1 chrome version",
39 | "last 1 firefox version",
40 | "last 1 safari version"
41 | ]
42 | },
43 | "engines": {
44 | "node": ">=16.14"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/apps/agentitive-site/sidebars.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Creating a sidebar enables you to:
3 | - create an ordered group of docs
4 | - render a sidebar for each doc of that group
5 | - provide next/previous navigation
6 |
7 | The sidebars can be generated from the filesystem, or explicitly defined here.
8 |
9 | Create as many sidebars as you want.
10 | */
11 |
12 | // @ts-check
13 |
14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
15 | const sidebars = {
16 | // By default, Docusaurus generates a sidebar from the docs folder structure
17 | docs: [{type: 'autogenerated', dirName: '.'}],
18 |
19 | // But you can create a sidebar manually
20 | /*
21 | tutorialSidebar: [
22 | 'intro',
23 | 'hello',
24 | {
25 | type: 'category',
26 | label: 'Tutorial',
27 | items: ['tutorial-basics/create-a-document'],
28 | },
29 | ],
30 | */
31 | };
32 |
33 | module.exports = sidebars;
34 |
--------------------------------------------------------------------------------
/apps/agentitive-site/src/components/HomepageFeatures/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import clsx from 'clsx';
3 | import styles from './styles.module.css';
4 |
5 | type FeatureItem = {
6 | title: string;
7 | Svg: React.ComponentType>;
8 | description: JSX.Element;
9 | };
10 |
11 | const FeatureList: FeatureItem[] = [
12 | {
13 | title: 'Easy to Use',
14 | Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default,
15 | description: (
16 | <>
17 | Docusaurus was designed from the ground up to be easily installed and
18 | used to get your website up and running quickly.
19 | >
20 | ),
21 | },
22 | {
23 | title: 'Focus on What Matters',
24 | Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default,
25 | description: (
26 | <>
27 | Docusaurus lets you focus on your docs, and we'll do the chores. Go
28 | ahead and move your docs into the docs
directory.
29 | >
30 | ),
31 | },
32 | {
33 | title: 'Powered by React',
34 | Svg: require('@site/static/img/undraw_docusaurus_react.svg').default,
35 | description: (
36 | <>
37 | Extend or customize your website layout by reusing React. Docusaurus can
38 | be extended while reusing the same header and footer.
39 | >
40 | ),
41 | },
42 | ];
43 |
44 | function Feature({title, Svg, description}: FeatureItem) {
45 | return (
46 |
47 |
48 |
49 |
50 |
51 |
{title}
52 |
{description}
53 |
54 |
55 | );
56 | }
57 |
58 | export default function HomepageFeatures(): JSX.Element {
59 | return (
60 |
61 |
62 |
63 | {FeatureList.map((props, idx) => (
64 |
65 | ))}
66 |
67 |
68 |
69 | );
70 | }
71 |
--------------------------------------------------------------------------------
/apps/agentitive-site/src/components/HomepageFeatures/styles.module.css:
--------------------------------------------------------------------------------
1 | .features {
2 | display: flex;
3 | align-items: center;
4 | padding: 2rem 0;
5 | width: 100%;
6 | }
7 |
8 | .featureSvg {
9 | height: 200px;
10 | width: 200px;
11 | }
12 |
--------------------------------------------------------------------------------
/apps/agentitive-site/src/css/custom.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Any CSS included here will be global. The classic template
3 | * bundles Infima by default. Infima is a CSS framework designed to
4 | * work well for content-centric websites.
5 | */
6 |
7 | /* You can override the default Infima variables here. */
8 | :root {
9 | --ifm-color-primary: #2e8555;
10 | --ifm-color-primary-dark: #29784c;
11 | --ifm-color-primary-darker: #277148;
12 | --ifm-color-primary-darkest: #205d3b;
13 | --ifm-color-primary-light: #33925d;
14 | --ifm-color-primary-lighter: #359962;
15 | --ifm-color-primary-lightest: #3cad6e;
16 | --ifm-code-font-size: 95%;
17 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
18 |
19 | --ifm-footer-background-color: var(--ifm-header-background-color);
20 | }
21 |
22 | /* For readability concerns, you should choose a lighter palette in dark mode. */
23 | [data-theme='dark'] {
24 | --ifm-color-primary: #25c2a0;
25 | --ifm-color-primary-dark: #21af90;
26 | --ifm-color-primary-darker: #1fa588;
27 | --ifm-color-primary-darkest: #1a8870;
28 | --ifm-color-primary-light: #29d5b0;
29 | --ifm-color-primary-lighter: #32d8b4;
30 | --ifm-color-primary-lightest: #4fddbf;
31 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
32 | }
33 |
--------------------------------------------------------------------------------
/apps/agentitive-site/src/definitions.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.module.scss';
--------------------------------------------------------------------------------
/apps/agentitive-site/src/pages/index.module.css:
--------------------------------------------------------------------------------
1 | /**
2 | * CSS files with the .module.css suffix will be treated as CSS modules
3 | * and scoped locally.
4 | */
5 |
6 | .main {
7 | /* show img/logo.svg blown up in the background to the left */
8 | background: url(../../static/img/logo.svg) no-repeat left top;
9 | background-size: 100%;
10 |
11 | }
12 |
13 | @media screen and (max-width: 996px) {
14 | .heroBanner {
15 | padding: 2rem;
16 | }
17 | }
18 |
19 | .buttons {
20 | display: flex;
21 | align-items: center;
22 | justify-content: center;
23 | }
24 |
--------------------------------------------------------------------------------
/apps/agentitive-site/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import clsx from 'clsx';
3 | import Link from '@docusaurus/Link';
4 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
5 | import Layout from '@theme/Layout';
6 |
7 | import styles from './index.module.css';
8 |
9 |
10 | export default function Home(): JSX.Element {
11 | const {siteConfig} = useDocusaurusContext();
12 | return (
13 |
16 |
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/apps/agentitive-site/src/pages/markdown-page.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Markdown page example
3 | ---
4 |
5 | # Markdown page example
6 |
7 | You don't need React to write simple standalone pages.
8 |
--------------------------------------------------------------------------------
/apps/agentitive-site/static/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agentitive/spellbound/99c6e70f2aa74322d074d8b88c0b1cdef2d7acdf/apps/agentitive-site/static/.nojekyll
--------------------------------------------------------------------------------
/apps/agentitive-site/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agentitive/spellbound/99c6e70f2aa74322d074d8b88c0b1cdef2d7acdf/apps/agentitive-site/static/img/favicon.ico
--------------------------------------------------------------------------------
/apps/agentitive-site/static/img/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/agentitive-site/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | // This file is not used in compilation. It is here just for a nice editor experience.
3 | "extends": "@tsconfig/docusaurus/tsconfig.json",
4 | "compilerOptions": {
5 | "baseUrl": "."
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/apps/spellbound-shared/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "spellbound-shared",
3 | "version": "0.0.3",
4 | "description": "",
5 | "main": "dist/index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "tsc -b"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "devDependencies": {
14 | "node-rig": "workspace:^0.0.1",
15 | "typescript": "~5.0.3",
16 | "@types/node": "~18.15.11",
17 | "@types/vscode": "~1.77.0",
18 | "@types/vscode-webview": "^1.57.0"
19 | },
20 | "dependencies": {
21 | "zustand": "^4.1.4",
22 | "birpc": "~0.2.10"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/apps/spellbound-shared/src/birpc/birpc.ts:
--------------------------------------------------------------------------------
1 | export type ArgumentsType = T extends (...args: infer A) => any ? A : never
2 | export type ReturnType = T extends (...args: any) => infer R ? R : never
3 | export type PromisifyFn = ReturnType extends Promise
4 | ? T
5 | : (...args: ArgumentsType) => Promise>>
6 |
7 | export type BirpcResolver = (name: string, resolved: Function) => Function | undefined
8 |
9 | export interface ChannelOptions {
10 | /**
11 | * Function to post raw message
12 | */
13 | post: (data: any, ...extras: any[]) => any | Promise
14 | /**
15 | * Listener to receive raw message
16 | */
17 | on: (fn: (data: any, ...extras: any[]) => void) => any | Promise
18 | /**
19 | * Custom function to serialize data
20 | *
21 | * by default it passes the data as-is
22 | */
23 | serialize?: (data: any) => any
24 | /**
25 | * Custom function to deserialize data
26 | *
27 | * by default it passes the data as-is
28 | */
29 | deserialize?: (data: any) => any
30 | }
31 |
32 | export interface EventOptions {
33 | /**
34 | * Names of remote functions that do not need response.
35 | */
36 | eventNames?: (keyof Remote)[]
37 |
38 | /**
39 | * Maximum timeout for waiting for response, in milliseconds.
40 | *
41 | * @default 60_000
42 | */
43 | timeout?: number
44 |
45 | /**
46 | * Custom resolver to resolve function to be called
47 | *
48 | * For advanced use cases only
49 | */
50 | resolver?: BirpcResolver
51 |
52 | /**
53 | * Custom error handler
54 | */
55 | onError?: (error: Error, functionName: string, args: any[]) => boolean | void
56 | }
57 |
58 | export type BirpcOptions = EventOptions & ChannelOptions
59 |
60 | export type BirpcFn = PromisifyFn & {
61 | /**
62 | * Send event without asking for response
63 | */
64 | asEvent(...args: ArgumentsType): void
65 | }
66 |
67 | export interface BirpcGroupFn {
68 | /**
69 | * Call the remote function and wait for the result.
70 | */
71 | (...args: ArgumentsType): Promise>[]>
72 | /**
73 | * Send event without asking for response
74 | */
75 | asEvent(...args: ArgumentsType): void
76 | }
77 |
78 | export type BirpcReturn = {
79 | [K in keyof RemoteFunctions]: BirpcFn
80 | } & { $functions: LocalFunctions }
81 |
82 | export type BirpcGroupReturn = {
83 | [K in keyof RemoteFunctions]: BirpcGroupFn
84 | }
85 |
86 | export interface BirpcGroup {
87 | readonly clients: BirpcReturn[]
88 | readonly functions: LocalFunctions
89 | readonly broadcast: BirpcGroupReturn
90 | updateChannels(fn?: ((channels: ChannelOptions[]) => void)): BirpcReturn[]
91 | }
92 |
93 | interface Request {
94 | /**
95 | * Type
96 | */
97 | t: 'q'
98 | /**
99 | * ID
100 | */
101 | i?: string
102 | /**
103 | * Method
104 | */
105 | m: string
106 | /**
107 | * Arguments
108 | */
109 | a: any[]
110 | }
111 |
112 | interface Response {
113 | /**
114 | * Type
115 | */
116 | t: 's'
117 | /**
118 | * Id
119 | */
120 | i: string
121 | /**
122 | * Result
123 | */
124 | r?: any
125 | /**
126 | * Error
127 | */
128 | e?: any
129 | }
130 |
131 | type RPCMessage = Request | Response
132 |
133 | export const DEFAULT_TIMEOUT = 60_000 // 1 minute
134 |
135 | const defaultSerialize = (i: any) => i
136 | const defaultDeserialize = defaultSerialize
137 |
138 | export function createBirpc(
139 | functions: LocalFunctions,
140 | options: BirpcOptions,
141 | ): BirpcReturn {
142 | const {
143 | post,
144 | on,
145 | eventNames = [],
146 | serialize = defaultSerialize,
147 | deserialize = defaultDeserialize,
148 | resolver,
149 | timeout = DEFAULT_TIMEOUT,
150 | } = options
151 |
152 | const rpcPromiseMap = new Map()
153 |
154 | let _promise: Promise | any
155 |
156 | const rpc = new Proxy({}, {
157 | get(_, method: string) {
158 | if (method === '$functions')
159 | return functions
160 |
161 | const sendEvent = (...args: any[]) => {
162 | post(serialize({ m: method, a: args, t: 'q' }))
163 | }
164 | if (eventNames.includes(method as any)) {
165 | sendEvent.asEvent = sendEvent
166 | return sendEvent
167 | }
168 | const sendCall = async (...args: any[]) => {
169 | // Wait if `on` is promise
170 | await _promise
171 | return new Promise((resolve, reject) => {
172 | const id = nanoid()
173 | rpcPromiseMap.set(id, { resolve, reject })
174 | post(serialize({ m: method, a: args, i: id, t: 'q' }))
175 | if (timeout >= 0) {
176 | setTimeout(() => {
177 | reject(new Error(`[birpc] timeout on calling "${method}"`))
178 | rpcPromiseMap.delete(id)
179 | }, timeout)
180 | }
181 | })
182 | }
183 | sendCall.asEvent = sendEvent
184 | return sendCall
185 | },
186 | }) as BirpcReturn
187 |
188 | _promise = on(async (data, ...extra) => {
189 | const msg = deserialize(data) as RPCMessage
190 | if (msg.t === 'q') {
191 | const { m: method, a: args } = msg
192 | let result, error: any
193 | const fn = resolver
194 | ? resolver(method, (functions as any)[method])
195 | : (functions as any)[method]
196 |
197 | if (!fn) {
198 | error = new Error(`[birpc] function "${method}" not found`)
199 | }
200 | else {
201 | try {
202 | result = await fn.apply(rpc, args)
203 | }
204 | catch (e) {
205 | error = e
206 | }
207 | }
208 |
209 | if (msg.i) {
210 | if (error && options.onError)
211 | options.onError(error, method, args)
212 | post(serialize({ t: 's', i: msg.i, r: result, e: error }), ...extra)
213 | }
214 | }
215 | else {
216 | const { i: ack, r: result, e: error } = msg
217 | const promise = rpcPromiseMap.get(ack)
218 | if (promise) {
219 | if (error)
220 | promise.reject(error)
221 | else
222 | promise.resolve(result)
223 | }
224 | rpcPromiseMap.delete(ack)
225 | }
226 | })
227 |
228 | return rpc
229 | }
230 |
231 | const cacheMap = new WeakMap()
232 | export function cachedMap(items: T[], fn: ((i: T) => R)): R[] {
233 | return items.map((i) => {
234 | let r = cacheMap.get(i)
235 | if (!r) {
236 | r = fn(i)
237 | cacheMap.set(i, r)
238 | }
239 | return r
240 | })
241 | }
242 |
243 | export function createBirpcGroup(
244 | functions: LocalFunctions,
245 | channels: ChannelOptions[] | (() => ChannelOptions[]),
246 | options: EventOptions = {},
247 | ): BirpcGroup {
248 | const getChannels = () => typeof channels === 'function' ? channels() : channels
249 | const getClients = (channels = getChannels()) => cachedMap(channels, s => createBirpc(functions, { ...options, ...s }))
250 |
251 | const broadcastProxy = new Proxy({}, {
252 | get(_, method) {
253 | const client = getClients()
254 | const callbacks = client.map(c => (c as any)[method])
255 | const sendCall = (...args: any[]) => {
256 | return Promise.all(callbacks.map(i => i(...args)))
257 | }
258 | sendCall.asEvent = (...args: any[]) => {
259 | callbacks.map(i => i.asEvent(...args))
260 | }
261 | return sendCall
262 | },
263 | }) as BirpcGroupReturn
264 |
265 | function updateChannels(fn?: ((channels: ChannelOptions[]) => void)) {
266 | const channels = getChannels()
267 | fn?.(channels)
268 | return getClients(channels)
269 | }
270 |
271 | getClients()
272 |
273 | return {
274 | get clients() {
275 | return getClients()
276 | },
277 | functions,
278 | updateChannels,
279 | broadcast: broadcastProxy,
280 | /**
281 | * @deprecated use `broadcast`
282 | */
283 | // @ts-expect-error deprecated
284 | boardcast: broadcastProxy,
285 | }
286 | }
287 |
288 | // port from nanoid
289 | // https://github.com/ai/nanoid
290 | const urlAlphabet = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'
291 | function nanoid(size = 21) {
292 | let id = ''
293 | let i = size
294 | while (i--)
295 | id += urlAlphabet[(Math.random() * 64) | 0]
296 | return id
297 | }
--------------------------------------------------------------------------------
/apps/spellbound-shared/src/birpc/extension.ts:
--------------------------------------------------------------------------------
1 | import { Message } from "../types"
2 |
3 |
4 | export type ExtensionProcedures = {
5 | abort(): Promise,
6 | submit(messages: Message[]): Promise,
7 | saveToFile(messages: Message[]): Promise,
8 | }
9 |
--------------------------------------------------------------------------------
/apps/spellbound-shared/src/birpc/index.ts:
--------------------------------------------------------------------------------
1 | export * from './birpc';
2 | export * from './extension';
3 | export * from './webview';
4 |
--------------------------------------------------------------------------------
/apps/spellbound-shared/src/birpc/webview.ts:
--------------------------------------------------------------------------------
1 | export type WebviewProcedures = {
2 | start(): Promise,
3 | update(message: string): Promise,
4 | finish(): Promise,
5 | result(message: string): Promise,
6 | }
7 |
--------------------------------------------------------------------------------
/apps/spellbound-shared/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './birpc';
2 | export * from './types';
3 |
--------------------------------------------------------------------------------
/apps/spellbound-shared/src/types/Message.ts:
--------------------------------------------------------------------------------
1 | export type Message = {
2 | role: string
3 | content: string
4 | }
--------------------------------------------------------------------------------
/apps/spellbound-shared/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Message';
2 |
--------------------------------------------------------------------------------
/apps/spellbound-shared/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./node_modules/node-rig/profiles/default/tsconfig-base.json",
3 | "compilerOptions": {
4 | "types": ["node", "vscode"]
5 | }
6 | }
--------------------------------------------------------------------------------
/apps/spellbound-ui/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/apps/spellbound-ui/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "exportall.config.folderListener": [
3 | "/src/components/Util"
4 | ],
5 |
6 | // four spaces
7 | "editor.tabSize": 4,
8 | "editor.detectIndentation": false,
9 |
10 | // format on save
11 | "editor.formatOnSave": true,
12 | // fix imports
13 | "editor.codeActionsOnSave": {
14 | "source.fixAll.eslint": true
15 | },
16 |
17 | }
--------------------------------------------------------------------------------
/apps/spellbound-ui/README.md:
--------------------------------------------------------------------------------
1 | # `webview-ui` Directory
2 |
3 | This directory contains all of the code that will be executed within the webview context. It can be thought of as the place where all the "frontend" code of a webview is contained.
4 |
5 | Types of content that can be contained here:
6 |
7 | - Frontend framework code (i.e. React, Svelte, Vue, etc.)
8 | - JavaScript files
9 | - CSS files
10 | - Assets / resources (i.e. images, illustrations, etc.)
11 |
--------------------------------------------------------------------------------
/apps/spellbound-ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "spellbound-ui",
3 | "version": "0.0.3",
4 | "private": true,
5 | "scripts": {
6 | "start": "EXTEND_ESLINT=true react-scripts start",
7 | "build": "DISABLE_ESLINT_PLUGIN=true node ./scripts/build-react-no-split.js",
8 | "watch": "DISABLE_ESLINT_PLUGIN=true node ./scripts/build-react-no-split.js",
9 | "test": "EXTEND_ESLINT=true react-scripts test",
10 | "eject": "react-scripts eject"
11 | },
12 | "watch": {
13 | "build": {
14 | "patterns": [
15 | "src"
16 | ],
17 | "extensions": "ts,tsx,scss"
18 | }
19 | },
20 | "dependencies": {
21 | "@fortawesome/fontawesome-svg-core": "~6.4.0",
22 | "@fortawesome/free-solid-svg-icons": "~6.4.0",
23 | "@fortawesome/react-fontawesome": "~0.2.0",
24 | "@vscode/webview-ui-toolkit": "^1.0.0",
25 | "react": "^17.0.2",
26 | "react-dom": "^17.0.2",
27 | "react-draggable": "^4.4.5",
28 | "react-markdown": "^8.0.6",
29 | "react-pie-menu": "1.0.0-alpha.4",
30 | "reactflow": "^11.2.0",
31 | "styled-components": "~5.3.9",
32 | "zustand": "^4.1.4",
33 | "spellbound-shared": "workspace:^0.0.3",
34 | "birpc": "~0.2.10",
35 | "react-autosize-textarea": "~7.1.0"
36 | },
37 | "devDependencies": {
38 | "@trpc/server": "~10.18.0",
39 | "@types/node": "~18.15.11",
40 | "@types/react": "^17.0.2",
41 | "@types/react-dom": "^17.0.2",
42 | "@types/react-pie-menu": "~1.0.0",
43 | "@types/styled-components": "~5.1.26",
44 | "@types/vscode-webview": "^1.57.0",
45 | "react-scripts": "^5.0.1",
46 | "rewire": "^5.0.0",
47 | "sass": "~1.60.0",
48 | "typescript": "~5.0.3"
49 | },
50 | "eslintConfig": {
51 | "extends": [
52 | "react-app",
53 | "react-app/jest"
54 | ],
55 | "rules": {
56 | "no-unused-expressions": "off"
57 | }
58 | },
59 | "browserslist": {
60 | "production": [
61 | ">0.2%",
62 | "not dead",
63 | "not op_mini all"
64 | ],
65 | "development": [
66 | "last 1 chrome version",
67 | "last 1 firefox version",
68 | "last 1 safari version"
69 | ]
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/apps/spellbound-ui/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agentitive/spellbound/99c6e70f2aa74322d074d8b88c0b1cdef2d7acdf/apps/spellbound-ui/public/favicon.ico
--------------------------------------------------------------------------------
/apps/spellbound-ui/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 | @volley/solid-scenes Editor
15 |
16 |
17 | You need to enable JavaScript to run this app.
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/apps/spellbound-ui/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agentitive/spellbound/99c6e70f2aa74322d074d8b88c0b1cdef2d7acdf/apps/spellbound-ui/public/logo192.png
--------------------------------------------------------------------------------
/apps/spellbound-ui/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agentitive/spellbound/99c6e70f2aa74322d074d8b88c0b1cdef2d7acdf/apps/spellbound-ui/public/logo512.png
--------------------------------------------------------------------------------
/apps/spellbound-ui/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/apps/spellbound-ui/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/apps/spellbound-ui/scripts/build-react-no-split.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * A script that overrides some of the create-react-app build script configurations
5 | * in order to disable code splitting/chunking and rename the output build files so
6 | * they have no hash. (Reference: https://mtm.dev/disable-code-splitting-create-react-app).
7 | *
8 | * This is crucial for getting React webview code to run because VS Code expects a
9 | * single (consistently named) JavaScript and CSS file when configuring webviews.
10 | */
11 |
12 | const rewire = require("rewire");
13 | const defaults = rewire("react-scripts/scripts/build.js");
14 | const config = defaults.__get__("config");
15 |
16 | // Disable code splitting
17 | config.optimization.splitChunks = {
18 | cacheGroups: {
19 | default: false,
20 | },
21 | };
22 |
23 | // Disable code chunks
24 | config.optimization.runtimeChunk = false;
25 |
26 | // Rename main.{hash}.js to main.js
27 | config.output.filename = "static/js/[name].js";
28 |
29 | // Rename main.{hash}.css to main.css
30 | config.plugins[5].options.filename = "static/css/[name].css";
31 | config.plugins[5].options.moduleFilename = () => "static/css/main.css";
32 |
--------------------------------------------------------------------------------
/apps/spellbound-ui/src/backup.ts:
--------------------------------------------------------------------------------
1 | export default null
2 |
3 | // (function () {
4 | // const vscode = acquireVsCodeApi();
5 |
6 | // const sendButton = document.querySelector('#input-area button');
7 | // const inputField = document.querySelector('#input-area input');
8 | // const messageHistoryContainer = document.querySelector("#message-history");
9 |
10 | // const messages = []
11 |
12 | // sendButton.addEventListener("click", () => {
13 | // const prompt = inputField.value.trim()
14 | // if (prompt) {
15 | // // Pass the prompt to the extension
16 | // vscode.postMessage({
17 | // command: "sendPrompt",
18 | // messages: [...messages, { role: "system", content: prompt }],
19 | // })
20 | // inputField.value = ""
21 | // }
22 | // })
23 |
24 | // inputField.addEventListener('keypress', (event) => {
25 | // if (event.key === 'Enter') {
26 | // event.preventDefault();
27 | // sendButton.click();
28 | // }
29 | // });
30 |
31 | // function addMessage(message) {
32 | // const messageElement = document.createElement('div');
33 | // messageElement.className = message.type === 'input' ? 'user-message' : 'response-message';
34 | // messageElement.textContent = message.content;
35 | // messageHistoryContainer.appendChild(messageElement);
36 |
37 | // if (message.type === 'input') {
38 | // messageElement.innerHTML = message.rendered;
39 | // }
40 |
41 | // if (message.type === 'start' || message.type === 'input') {
42 | // messages.push({
43 | // role: message.type === 'input' ? 'system' : 'assistant',
44 | // content: message.content ?? "",
45 | // })
46 | // }
47 | // }
48 |
49 | // // Add a message event listener to handle 'addMessage' command
50 | // window.addEventListener('message', (event) => {
51 | // const message = event.data; // The JSON data our extension sent
52 | // switch (message.command) {
53 | // case 'sendMessage':
54 | // switch(message.message.type) {
55 | // case 'start':
56 | // addMessage(message.message);
57 | // break;
58 | // case 'chunk':
59 | // const lastResponse = document.querySelector('.response-message:last-child');
60 |
61 | // lastResponse.innerHTML = message.message.rendered;
62 |
63 | // messages[messages.length - 1].content += message.message.content;
64 | // break;
65 | // case 'done':
66 | // // Todo: re-enable the input area
67 | // break;
68 | // case 'input':
69 | // addMessage(message.message);
70 | // break;
71 | // case 'tool-result':
72 | // // Treat the tool result as a prompt (implement 'thought-loop')
73 | // vscode.postMessage({
74 | // command: "sendPrompt",
75 | // messages: [...messages, { role: "system", content: message.message.content }]
76 | // })
77 | // break;
78 | // }
79 | // break;
80 | // }
81 | // });
82 | // })();`
--------------------------------------------------------------------------------
/apps/spellbound-ui/src/components/App/index.tsx:
--------------------------------------------------------------------------------
1 | import { InputPanel } from "../InputPanel";
2 | import { MessageHistory } from "../MessageHistory";
3 | import styles from "./styles.module.scss"
4 |
5 |
6 | export function App() {
7 | return (
8 |
9 | Spellbound 💫
10 |
11 |
12 |
13 | );
14 | }
--------------------------------------------------------------------------------
/apps/spellbound-ui/src/components/App/styles.module.scss:
--------------------------------------------------------------------------------
1 | .app {
2 | height: 100%;
3 | display: "flex";
4 | flex-direction: column;
5 | justify-content: "stretch";
6 | align-items: "stretch";
7 |
8 | & > h1 {
9 | text-align: center;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/apps/spellbound-ui/src/components/InputPanel/InputPanel.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import AutosizeTextArea from "react-autosize-textarea";
3 |
4 | import styles from "./styles.module.scss"
5 |
6 | import { rpc } from "../../utilities/rpc";
7 | import useStore from "../../store";
8 |
9 | export function InputPanel() {
10 | const { addMessage, messages, isThinking, setIsThinking } = useStore(state => ({
11 | addMessage: state.addMessage,
12 | messages: state.messages,
13 | isThinking: state.isThinking,
14 | setIsThinking: state.setIsThinking,
15 | }))
16 | const [input, setInput] = useState("");
17 |
18 | const onClick = async () => {
19 | const prompt = input.trim()
20 | if (prompt) {
21 | const newMessage = { role: "user", content: prompt }
22 | addMessage(newMessage)
23 | const newMessages = [...messages, newMessage]
24 | console.log("submitting new messages...")
25 | await rpc.submit(newMessages)
26 | setInput("")
27 | }
28 | }
29 |
30 | const onAbort = async () => {
31 | await rpc.abort()
32 | setIsThinking(false)
33 | }
34 |
35 | const onKeyDown = (e: React.KeyboardEvent) => {
36 | if (e.key === "Enter") {
37 | e.preventDefault()
38 | onClick()
39 | }
40 | }
41 |
42 | const saveToFile = async () => {
43 | await rpc.saveToFile(messages)
44 | }
45 |
46 | return (
47 |
48 |
) => setInput(e.currentTarget.value)}
54 | />
55 | Send
60 | {isThinking &&
63 | Abort
64 | }
65 | {!isThinking && Export }
70 |
71 | );
72 | }
73 |
--------------------------------------------------------------------------------
/apps/spellbound-ui/src/components/InputPanel/index.ts:
--------------------------------------------------------------------------------
1 | export * from './InputPanel';
2 |
--------------------------------------------------------------------------------
/apps/spellbound-ui/src/components/InputPanel/styles.module.scss:
--------------------------------------------------------------------------------
1 | .inputPanel {
2 | position: absolute;
3 | bottom: 0;
4 | width: 95%;
5 | padding: 8px;
6 | display: flex;
7 |
8 | & > textarea {
9 | flex-grow: 1;
10 | outline: none;
11 | border: none;
12 | border-radius: 2px;
13 | padding: 6px;
14 | font-size: 14px;
15 | color: #CCC;
16 | background-color: #0e1116;
17 | resize: none;
18 | font: inherit;
19 | }
20 |
21 | & > input:focus {
22 | border: 1px solid #007fd4;
23 | }
24 |
25 | & > button {
26 | border: none;
27 | color: white;
28 | padding: 6px 12px;
29 | cursor: pointer;
30 | margin-left: 4px;
31 | font-size: 14px;
32 | border-radius: 2px;
33 | outline: none;
34 | }
35 |
36 | & > .send {
37 | background-color: #438440;
38 | }
39 |
40 | & > .send:hover {
41 | background-color: #488848;
42 | }
43 |
44 | & > .export {
45 | background-color: #000000;
46 | }
47 |
48 | & > .export:hover {
49 | background-color: #111111;
50 | }
51 |
52 | & > button:disabled {
53 | background-color: #666;
54 | cursor: not-allowed;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/apps/spellbound-ui/src/components/MessageHistory/MessageHistory.tsx:
--------------------------------------------------------------------------------
1 | import useStore from "../../store"
2 | import { MessageItem } from "../MessageItem"
3 |
4 | import styles from "./styles.module.scss"
5 |
6 |
7 | export const MessageHistory = () => {
8 | const { messages } = useStore(state => ({ messages: state.messages }))
9 |
10 | return
11 | {messages.map((message, index) => (
12 |
13 | ))}
14 |
15 | }
--------------------------------------------------------------------------------
/apps/spellbound-ui/src/components/MessageHistory/index.ts:
--------------------------------------------------------------------------------
1 | export * from './MessageHistory';
2 |
--------------------------------------------------------------------------------
/apps/spellbound-ui/src/components/MessageHistory/styles.module.scss:
--------------------------------------------------------------------------------
1 | .messageHistory {
2 | position: relative;
3 | overflow-y: auto;
4 | padding: 16px;
5 | height: calc(100vh - 145px);
6 | }
--------------------------------------------------------------------------------
/apps/spellbound-ui/src/components/MessageItem/MessageItem.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from "react"
2 |
3 | import ReactMarkdown from 'react-markdown'
4 |
5 | import styles from "./styles.module.scss"
6 | import { Message } from "spellbound-shared"
7 |
8 |
9 | export type MessageItemProps = {
10 | message: Message
11 | }
12 |
13 | const styleForRole = (role: string) => {
14 | switch (role) {
15 | case "user":
16 | return styles.user
17 | case "assistant":
18 | return styles.assistant
19 | case "system":
20 | return styles.system
21 | }
22 | }
23 |
24 |
25 | export const MessageItem: FC = ({ message }) => {
26 | return
27 | {/*
{message.role}
*/}
28 |
29 |
30 |
31 |
32 | }
--------------------------------------------------------------------------------
/apps/spellbound-ui/src/components/MessageItem/index.ts:
--------------------------------------------------------------------------------
1 | export * from './MessageItem';
2 |
--------------------------------------------------------------------------------
/apps/spellbound-ui/src/components/MessageItem/styles.module.scss:
--------------------------------------------------------------------------------
1 | .system {
2 | background-color: rgba(255, 255, 255, 0.2);
3 | color: white;
4 | }
5 |
6 | .user {
7 | background-color: rgba(255, 255, 255, 0.1);
8 | color: rgb(220, 220, 220);
9 | }
10 |
11 | .assistant {
12 | background-color: rgba(255, 255, 255, 0.2);
13 | color: rgb(236, 236, 236);
14 | }
15 |
16 | .messageItem {
17 | display: flex;
18 | flex-direction: column;
19 |
20 | padding: 8px;
21 | margin-bottom: 8px;
22 | border-radius: 4px;
23 |
24 |
25 | .role {
26 | display: flex;
27 | flex-direction: row;
28 | justify-content: flex-start;
29 | align-items: center;
30 | }
31 |
32 | .content {
33 | display: flex;
34 | flex-direction: column;
35 | overflow-x: auto;
36 | }
37 | }
--------------------------------------------------------------------------------
/apps/spellbound-ui/src/components/index.ts:
--------------------------------------------------------------------------------
1 | export * from './App';
2 | export * from './styles';
3 |
--------------------------------------------------------------------------------
/apps/spellbound-ui/src/components/styles.ts:
--------------------------------------------------------------------------------
1 | export const max = {
2 | height: "100vh",
3 | display: "flex",
4 | justifyContent: "stretch",
5 | alignItems: "stretch",
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/apps/spellbound-ui/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { App } from "./components";
4 |
5 | ReactDOM.render(
6 |
7 |
8 | ,
9 | document.getElementById("root")
10 | );
11 |
--------------------------------------------------------------------------------
/apps/spellbound-ui/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/apps/spellbound-ui/src/store.ts:
--------------------------------------------------------------------------------
1 | import { create } from 'zustand';
2 | import { Message } from 'spellbound-shared/src/types';
3 |
4 |
5 | export type DocumentState = {
6 | isThinking: boolean,
7 | messages: Message[],
8 |
9 | setIsThinking(isThinking: boolean): void,
10 | clearMessages(): void,
11 | addMessage(message: Message): void,
12 | updateMessage(index: number, content: string): void,
13 | };
14 |
15 | // this is our useStore hook that we can use in our components to get parts of the store and call actions
16 | const useStore = create((set, get) => ({
17 | isThinking: false,
18 | messages: [],
19 |
20 | setIsThinking: (isThinking: boolean) => set({ isThinking }),
21 | clearMessages: () => set({ messages: [] }),
22 | addMessage: (message: Message) => set(state => ({ messages: [...state.messages, message] })),
23 | updateMessage: (index: number, content: string) => set(state => {
24 | const messages = [...state.messages];
25 | messages[index] = { role: messages[index].role, content };
26 | return { messages };
27 | }),
28 | }));
29 |
30 | export default useStore;
--------------------------------------------------------------------------------
/apps/spellbound-ui/src/styles.scss:
--------------------------------------------------------------------------------
1 |
2 | pre { white-space: pre-wrap; word-wrap: break-word; }
3 | code { display: block; padding: 0.5em; overflow-x: auto; }
4 |
5 |
6 |
7 | #message-history {
8 | position: relative;
9 | overflow-y: auto;
10 | padding: 16px;
11 | height: calc(100% - 70px);
12 | }
13 |
14 | #input-area {
15 | position: absolute;
16 | bottom: 0;
17 | width: 95%;
18 | padding: 8px;
19 | display: flex;
20 | }
21 |
22 | #input-area input {
23 | flex-grow: 1;
24 | outline: none;
25 | border: none;
26 | border-radius: 2px;
27 | padding: 6px;
28 | font-size: 14px;
29 | color: #CCC;
30 | background-color: #0e1116;
31 | }
32 |
33 | #input-area input:focus {
34 | border: 1px solid #007fd4;
35 | }
36 |
37 | #input-area button {
38 | background-color: #438440;
39 | border: none;
40 | color: white;
41 | padding: 6px 12px;
42 | cursor: pointer;
43 | margin-left: 4px;
44 | font-size: 14px;
45 | border-radius: 2px;
46 | outline: none;
47 | }
48 |
49 | #input-area button:hover {
50 | background-color: #488848;
51 | }
52 |
53 | .user-message {
54 | background-color: rgba(255, 255, 255, 0.1);
55 | padding: 8px;
56 | margin-bottom: 8px;
57 | border-radius: 4px;
58 | }
59 |
60 | .response-message {
61 | background-color: rgba(255, 255, 255, 0.2);
62 | padding: 8px;
63 | margin-bottom: 8px;
64 | border-radius: 4px;
65 | }
--------------------------------------------------------------------------------
/apps/spellbound-ui/src/utilities/rpc.ts:
--------------------------------------------------------------------------------
1 | import { vscode } from "./vscode";
2 | import useStore from "../store";
3 | import { ExtensionProcedures, createBirpc } from "spellbound-shared";
4 |
5 | export const rpc = createBirpc({
6 | start: async () => {
7 | const { setIsThinking, addMessage } = useStore.getState();
8 |
9 | setIsThinking(true);
10 | addMessage({
11 | role: 'assistant',
12 | content: ''
13 | });
14 | },
15 | update: async (message: string) => {
16 | const { messages, updateMessage } = useStore.getState();
17 | const lastMessage = messages[messages.length - 1];
18 | updateMessage(messages.length - 1, lastMessage.content + message)
19 | },
20 | finish: async () => {
21 | const { setIsThinking } = useStore.getState();
22 | setIsThinking(false);
23 | },
24 | result: async (message: string) => {
25 | const { addMessage } = useStore.getState();
26 | addMessage({
27 | role: 'system',
28 | content: message
29 | })
30 | },
31 | }, {
32 | post: data => {
33 | vscode.postMessage(data)
34 | },
35 | on: cb => {
36 | window.addEventListener('message', event => {
37 | cb(event.data)
38 | })
39 | }
40 | })
41 |
--------------------------------------------------------------------------------
/apps/spellbound-ui/src/utilities/vscode.ts:
--------------------------------------------------------------------------------
1 | import type { WebviewApi } from "vscode-webview";
2 |
3 | /**
4 | * A utility wrapper around the acquireVsCodeApi() function, which enables
5 | * message passing and state management between the webview and extension
6 | * contexts.
7 | *
8 | * This utility also enables webview code to be run in a web browser-based
9 | * dev server by using native web browser features that mock the functionality
10 | * enabled by acquireVsCodeApi.
11 | */
12 | class VSCodeAPIWrapper {
13 | private readonly vsCodeApi: WebviewApi | undefined;
14 |
15 | constructor() {
16 | // Check if the acquireVsCodeApi function exists in the current development
17 | // context (i.e. VS Code development window or web browser)
18 | if (typeof acquireVsCodeApi === "function") {
19 | this.vsCodeApi = acquireVsCodeApi();
20 | }
21 | }
22 |
23 | /**
24 | * Post a message (i.e. send arbitrary data) to the owner of the webview.
25 | *
26 | * @remarks When running webview code inside a web browser, postMessage will instead
27 | * log the given message to the console.
28 | *
29 | * @param message Abitrary data (must be JSON serializable) to send to the extension context.
30 | */
31 | public postMessage(message: unknown) {
32 | if (this.vsCodeApi) {
33 | this.vsCodeApi.postMessage(message);
34 | } else {
35 | console.log(message);
36 | }
37 | }
38 |
39 | /**
40 | * Get the persistent state stored for this webview.
41 | *
42 | * @remarks When running webview source code inside a web browser, getState will retrieve state
43 | * from local storage (https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
44 | *
45 | * @return The current state or `undefined` if no state has been set.
46 | */
47 | public getState(): unknown | undefined {
48 | if (this.vsCodeApi) {
49 | return this.vsCodeApi.getState();
50 | } else {
51 | const state = localStorage.getItem("vscodeState");
52 | return state ? JSON.parse(state) : undefined;
53 | }
54 | }
55 |
56 | /**
57 | * Set the persistent state stored for this webview.
58 | *
59 | * @remarks When running webview source code inside a web browser, setState will set the given
60 | * state using local storage (https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
61 | *
62 | * @param newState New persisted state. This must be a JSON serializable object. Can be retrieved
63 | * using {@link getState}.
64 | *
65 | * @return The new state.
66 | */
67 | public setState(newState: T): T {
68 | if (this.vsCodeApi) {
69 | return this.vsCodeApi.setState(newState);
70 | } else {
71 | localStorage.setItem("vscodeState", JSON.stringify(newState));
72 | return newState;
73 | }
74 | }
75 | }
76 |
77 | // Exports class singleton to prevent multiple invocations of acquireVsCodeApi.
78 | export const vscode = new VSCodeAPIWrapper();
79 |
--------------------------------------------------------------------------------
/apps/spellbound-ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx",
18 | "strictPropertyInitialization": false
19 | },
20 | "include": ["src", "src/store.ts"]
21 | }
22 |
--------------------------------------------------------------------------------
/apps/spellbound-ui/webpack.config.js:
--------------------------------------------------------------------------------
1 | const MiniCssExtractPlugin = require("mini-css-extract-plugin")
2 |
3 | module.exports = {
4 | mode: "production",
5 | entry: "./src/index.tsx",
6 | output: {
7 | path: __dirname + "/build",
8 | publicPath: "/",
9 | filename: "static/js/main.js",
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.js$/,
15 | exclude: /node_modules/,
16 | use: {
17 | loader: "babel-loader",
18 | },
19 | },
20 | {
21 | test: /\.css$/,
22 | use: [MiniCssExtractPlugin.loader, "css-loader"],
23 | },
24 | ],
25 | },
26 | plugins: [
27 | new MiniCssExtractPlugin({
28 | filename: "static/css/main.css",
29 | }),
30 | ],
31 | optimization: {
32 | splitChunks: {
33 | cacheGroups: {
34 | default: false,
35 | },
36 | },
37 | runtimeChunk: false,
38 | },
39 | }
40 |
--------------------------------------------------------------------------------
/apps/spellbound/.eslintignore:
--------------------------------------------------------------------------------
1 | **/*.md
2 |
--------------------------------------------------------------------------------
/apps/spellbound/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "parserOptions": {
5 | "ecmaVersion": 6,
6 | "sourceType": "module"
7 | },
8 | "plugins": ["@typescript-eslint"],
9 | "rules": {
10 | "@typescript-eslint/naming-convention": "off",
11 | "@typescript-eslint/semi": ["warn", "never"],
12 | "curly": "warn",
13 | "eqeqeq": "warn",
14 | "no-throw-literal": "warn",
15 | "semi": "off"
16 | },
17 | "ignorePatterns": ["out", "dist", "**/*.d.ts"],
18 | "overrides": [
19 | {
20 | "files": ["*.json"],
21 | "rules": {
22 | "@typescript-eslint/naming-convention": "off"
23 | }
24 | }
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/apps/spellbound/.gitattributes:
--------------------------------------------------------------------------------
1 | # Don't allow people to merge changes to these generated files, because the result
2 | # may be invalid. You need to run "rush update" again.
3 | pnpm-lock.yaml merge=text
4 | shrinkwrap.yaml merge=binary
5 | npm-shrinkwrap.json merge=binary
6 | yarn.lock merge=binary
7 |
8 | # Rush's JSON config files use JavaScript-style code comments. The rule below prevents pedantic
9 | # syntax highlighters such as GitHub's from highlighting these comments as errors. Your text editor
10 | # may also require a special configuration to allow comments in JSON.
11 | #
12 | # For more information, see this issue: https://github.com/microsoft/rushstack/issues/1088
13 | #
14 | *.json linguist-language=JSON-with-Comments
15 |
--------------------------------------------------------------------------------
/apps/spellbound/.gitignore:
--------------------------------------------------------------------------------
1 | out
2 | dist
3 | node_modules
4 | .vscode-test/
5 | *.vsix
6 | .DS_Store
7 | static
--------------------------------------------------------------------------------
/apps/spellbound/.spellbound/description.md:
--------------------------------------------------------------------------------
1 | This is the Spellbound VSCode extension repo. It's a typescript project. The code is in src/ It uses a webview to offer a chat-like experience for interacting with the Spellbound agent, which is you.
2 |
3 | You can test code changes with `npm run build`
--------------------------------------------------------------------------------
/apps/spellbound/.spellbound/pinecone_cache.json:
--------------------------------------------------------------------------------
1 | {
2 | ".vscode": ""
3 | }
4 |
--------------------------------------------------------------------------------
/apps/spellbound/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Launch Extension",
9 | "type": "extensionHost",
10 | "request": "launch",
11 | "cwd": "${workspaceFolder}",
12 | "args": [
13 | "--extensionDevelopmentPath=${workspaceFolder}"
14 | ],
15 | "outFiles": [
16 | "${workspaceFolder}/dist/**/*.js"
17 | ],
18 | // run rushx build in apps/examples/flow-editor first
19 | // "preLaunchTask": "rush build"
20 | }
21 | ]
22 | }
--------------------------------------------------------------------------------
/apps/spellbound/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agentitive/spellbound/99c6e70f2aa74322d074d8b88c0b1cdef2d7acdf/apps/spellbound/assets/icon.png
--------------------------------------------------------------------------------
/apps/spellbound/assets/prompt.md:
--------------------------------------------------------------------------------
1 | You are an AI agent that needs to complete a high-level task in an unknown codebase using Thought-Action chain-of-reasoning.
2 |
3 | 1. Your responses always have both "## Thought" and "## Action".
4 | 2. The "## Thought" contains your reasoning, subgoals and action explanation.
5 | 3. The "## Action" contains a YAML structure with 'tool' key and any other required arguments.
6 | 4. Only one tool can be used per interaction, with "done" marking final conclusion.
7 |
8 | **Codebase Description**: <%= codebase_description %>
9 |
10 | **Language**: <%= language %>
11 |
12 | **Current File Name**: <%= current_file_name %>
13 |
14 | Available Actions:
15 |
16 | <%= toolList %>
17 |
18 | EXAMPLE THOUGHT/ACTION PAIR:
19 |
20 | ## Thought
21 |
22 | I need to find the file that contains the function that is called when the user clicks on the "Submit" button.
23 |
24 | ## Action
25 |
26 | ```yaml
27 | tool: grep
28 | regex: "submit"
29 | ```
30 |
31 | CONVERSATION:
32 |
--------------------------------------------------------------------------------
/apps/spellbound/assets/wand-magic-sparkles-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/spellbound/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "spellbound",
3 | "displayName": "spellbound",
4 | "description": "Use OpenAI models to weave the magic of AI into your coding experience, generating enchanting solutions and sparking intuitive insights.",
5 | "license": "MIT",
6 | "publisher": "mpoteat-vsce",
7 | "icon": "./assets/icon.png",
8 | "galleryBanner.color": "#0e1116",
9 | "version": "0.0.4",
10 | "engines": {
11 | "vscode": "^1.77.0"
12 | },
13 | "categories": [
14 | "Other"
15 | ],
16 | "activationEvents": [
17 | "onStartupFinished"
18 | ],
19 | "main": "./dist/extension.js",
20 | "repository": {
21 | "type": "https",
22 | "url": "https://github.com/agentitive/spellbound.git"
23 | },
24 | "contributes": {
25 | "menus": {
26 | "explorer/context": [
27 | {
28 | "command": "spellbound.indexFolder",
29 | "when": "explorerResourceIsFolder"
30 | }
31 | ]
32 | },
33 | "configuration": {
34 | "title": "Spellbound",
35 | "properties": {
36 | "spellbound.openai_api_key": {
37 | "type": "string",
38 | "default": "",
39 | "description": "Enter your OpenAI API key here, used for inference."
40 | },
41 | "spellbound.pinecone_api_key": {
42 | "type": "string",
43 | "default": "",
44 | "description": "Enter your Pinecone API key here, used for search."
45 | },
46 | "spellbound.pinecone_index": {
47 | "type": "string",
48 | "default": "spellbound",
49 | "description": "Enter your Pinecone index name here, used for search."
50 | },
51 | "spellbound.model": {
52 | "type": "string",
53 | "default": "gpt-4",
54 | "description": "Select the AI model used by the Spellbound extension."
55 | },
56 | "spellbound.agent.stopOnError": {
57 | "type": "boolean",
58 | "default": "true",
59 | "description": "Does the AI stop when it encounters an error?"
60 | }
61 | }
62 | },
63 | "viewsContainers": {
64 | "activitybar": [
65 | {
66 | "id": "spellbound",
67 | "type": "webview",
68 | "title": "Spellbound",
69 | "icon": "assets/wand-magic-sparkles-solid.svg"
70 | }
71 | ]
72 | },
73 | "views": {
74 | "spellbound": [
75 | {
76 | "id": "spellbound.chat",
77 | "type": "webview",
78 | "name": ""
79 | }
80 | ]
81 | },
82 | "commands": [
83 | {
84 | "command": "spellbound.executeInstruction",
85 | "title": "Spellbound: Execute Instruction"
86 | },
87 | {
88 | "command": "spellbound.indexWorkspace",
89 | "title": "Spellbound: Index the current workspace"
90 | },
91 | {
92 | "command": "spellbound.indexFolder",
93 | "title": "Spellbound: Index the folder contents"
94 | }
95 | ]
96 | },
97 | "scripts": {
98 | "vscode:prepublish": "npm run package",
99 | "build": "webpack",
100 | "watch": "webpack",
101 | "package": "webpack --mode production --devtool hidden-source-map",
102 | "lint": "eslint src --ext ts"
103 | },
104 | "devDependencies": {
105 | "@types/ejs": "^3.1.2",
106 | "@types/markdown-it": "^12.2.3",
107 | "@types/node": "~18.15.11",
108 | "@types/vscode": "~1.77.0",
109 | "@typescript-eslint/eslint-plugin": "^5.56.0",
110 | "@typescript-eslint/parser": "^5.56.0",
111 | "eslint": "^8.36.0",
112 | "ts-loader": "^9.4.2",
113 | "typescript": "~5.0.3",
114 | "webpack": "^5.76.3",
115 | "webpack-cli": "^5.0.1",
116 | "copy-webpack-plugin": "~11.0.0",
117 | "spellbound-ui": "workspace:^0.0.3",
118 | "@types/diff": "~5.0.3"
119 | },
120 | "dependencies": {
121 | "@pinecone-database/pinecone": "~4.0.0",
122 | "spellbound-shared": "workspace:^0.0.3",
123 | "ejs": "^3.1.9",
124 | "ignore": "^5.2.4",
125 | "markdown-it": "^13.0.1",
126 | "node-fetch": "^3.3.1",
127 | "yaml": "^2.2.1",
128 | "diff": "~5.1.0",
129 | "globby": "~13.1.3"
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/apps/spellbound/src/agent/AgentLogicHandler.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode"
2 |
3 | import { BirpcReturn, Message, WebviewProcedures } from "spellbound-shared"
4 | import { fillPrompt } from "../prompts/fillPrompt"
5 | import YAML from "yaml"
6 | import { AnyToolInterface } from "../tools/AnyToolInterface"
7 | import { ToolEngine } from "../tools/ToolEngine"
8 | import { InferenceService } from "../inference/InferenceHandler"
9 |
10 | export class AgentLogicHandler {
11 | aborted = false
12 | inferences: InferenceService
13 |
14 | constructor(private readonly rpc: BirpcReturn) {
15 | this.inferences = new InferenceService()
16 | }
17 |
18 | async abort() {
19 | this.aborted = true
20 | this.inferences.abort()
21 | await this.rpc.finish()
22 | }
23 |
24 | async promptAgent(messages: Message[]) {
25 | const updatedMessages = await this.createUpdatedMessages(messages)
26 | this.aborted = false
27 | this.inferences.infer(
28 | updatedMessages,
29 | () => this.onStartHandler(),
30 | (data: string) => this.onDataHandler(data),
31 | (result: string) => this.onEndHandler(messages, result)
32 | )
33 | }
34 |
35 | private async createUpdatedMessages(messages: Message[]): Promise {
36 | const userMessages = messages.filter((m) => m.role === "user")
37 | const filledPrompt = await fillPrompt({
38 | task: userMessages[userMessages.length - 1].content,
39 | })
40 |
41 | return [
42 | {
43 | role: "system",
44 | content: filledPrompt,
45 | },
46 | ...messages,
47 | ]
48 | }
49 |
50 | private async onStartHandler() {
51 | await this.rpc.start()
52 | }
53 |
54 | private async onDataHandler(output: string) {
55 | await this.rpc.update(output)
56 | }
57 |
58 | private async onEndHandler(messages: Message[], result: string) {
59 | await this.rpc.finish()
60 |
61 | if (this.aborted) {
62 | return
63 | }
64 |
65 | this.handleToolMessage([
66 | ...messages,
67 | {
68 | role: "assistant",
69 | content: result,
70 | },
71 | ])
72 | }
73 |
74 | private async sendResultMessage(messages: Message[], toolResult: string) {
75 | const resultMessage = await this.createResultMessage(toolResult)
76 | await this.rpc.result(resultMessage.content)
77 |
78 | if (!this.aborted) {
79 | // TODO: make this a setting "run until done"
80 | // this causes the agent to respond to result messages
81 | await this.promptAgent([...messages, resultMessage])
82 | }
83 | }
84 |
85 | private async handleToolMessage(messages: Message[]) {
86 | if (this.aborted) {
87 | return
88 | }
89 |
90 | const actionObject = await this.extractActionObject(messages)
91 | if (actionObject) {
92 | const toolResult = await this.executeToolAction(actionObject)
93 |
94 | if (toolResult) {
95 | await this.sendResultMessage(messages, toolResult)
96 | }
97 | } else {
98 | const warningResult = `No tool action found in message.`
99 | console.warn("warningResult:", messages[messages.length - 1].content)
100 | await this.sendResultMessage(messages, warningResult)
101 | }
102 | }
103 |
104 | private async extractActionObject(messages: Message[]) {
105 | const lastMessage = messages[messages.length - 1]
106 | const actionRegex = /^\s*##\s*Action\s*\n+```[\s\S]*?\n([^]+?)```/gm
107 | const match = actionRegex.exec(lastMessage.content)
108 |
109 | if (match) {
110 | try {
111 | return YAML.parse(match[1], { strict: false })
112 | } catch (err: any) {
113 | console.error("FULL_MESSAGE", lastMessage.content)
114 | console.error("REGEX_MATCH", match[1])
115 | console.error("Error parsing tool action:", err)
116 | // get the extension setting .agent.stopOnError
117 | const errorMessage = `ERROR: Could not parse tool action: ${err?.message}`
118 |
119 | const stopOnError = vscode.workspace
120 | .getConfiguration("spellbound")
121 | .get("agent.stopOnError")
122 | if (stopOnError) {
123 | return await this.rpc.result(errorMessage)
124 | }
125 |
126 | await this.sendResultMessage(messages, errorMessage)
127 | }
128 | }
129 | return null
130 | }
131 |
132 | private async createResultMessage(toolResult: string): Promise {
133 | const toolResultOutput = `## Result\n\`\`\`\n${toolResult}\n\`\`\``
134 | return {
135 | role: "system",
136 | content: toolResultOutput,
137 | }
138 | }
139 |
140 | private async executeToolAction(
141 | actionObject: AnyToolInterface
142 | ): Promise {
143 | if (!actionObject.tool) {
144 | return
145 | }
146 |
147 | try {
148 | const tool = actionObject.tool
149 | const [impl] = ToolEngine[tool]
150 | return await impl(actionObject as never)
151 | } catch (error) {
152 | console.log(error)
153 | throw error
154 |
155 | return `ERROR: Unknown tool: ${(actionObject as any)?.tool}`
156 | }
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/apps/spellbound/src/agent/index.ts:
--------------------------------------------------------------------------------
1 | export * from './AgentLogicHandler';
2 |
--------------------------------------------------------------------------------
/apps/spellbound/src/api/openai.ts:
--------------------------------------------------------------------------------
1 | import * as https from "https"
2 | import { getOpenAIKey } from "../environment/getOpenAIKey"
3 | import { getCurrentModel } from "../environment/getCurrentModel"
4 | import fetch from "node-fetch"
5 | import { Chunk } from "../utils/chunking"
6 | import { Message } from "spellbound-shared"
7 |
8 | type Callbacks = {
9 | onData: (chunk: string) => void
10 | onError?: (error: Error) => void
11 | onEnd?: () => void
12 | }
13 |
14 | type MessageCompletionChunk = {
15 | id: string
16 | object: "chat.completion.chunk"
17 | created: number
18 | model: string
19 | choices: {
20 | delta: { content: string }
21 | index: number
22 | finish_reason: null | string
23 | }[]
24 | }
25 |
26 | function httpRequest(options: https.RequestOptions): Promise {
27 | return new Promise((resolve, reject) => {
28 | const req = https.request(options, (res) => {
29 | let data = ""
30 |
31 | res.on("data", (chunk) => {
32 | data += chunk
33 | })
34 |
35 | res.on("end", () => {
36 | if (res.statusCode === 200) {
37 | resolve(data)
38 | } else {
39 | reject(new Error(`Request failed. Status code: ${res.statusCode}`))
40 | }
41 | })
42 | })
43 |
44 | req.on("error", reject)
45 | req.end()
46 | })
47 | }
48 |
49 | export async function listModels(apiKey: string): Promise {
50 | try {
51 | const options: https.RequestOptions = {
52 | hostname: "api.openai.com",
53 | path: "/v1/models",
54 | method: "GET",
55 | headers: {
56 | Authorization: `Bearer ${apiKey}`,
57 | "Content-Type": "application/json",
58 | },
59 | }
60 |
61 | const data = await httpRequest(options)
62 | const models: string[] = JSON.parse(data).data.map((x: any) => x.id)
63 | return models
64 | } catch (error) {
65 | console.error("Error fetching models:", error)
66 | return []
67 | }
68 | }
69 |
70 | type PossibleEndpointError = {
71 | error?: {
72 | message?: string | null
73 | type?: string | null
74 | param?: string | null
75 | code?: string | null
76 | } | null
77 | } | null
78 |
79 | const nonRetryErrorTypes = ["tokens", "invalid_request_error"]
80 |
81 | function parseCompletionDataChunk(buffer: Buffer): string {
82 | const contentDelimiter = "\n\n"
83 | const jsonDataPrefix = "data: "
84 |
85 | const bufferString = buffer.toString("utf-8")
86 | const bufferChunks = bufferString.split(contentDelimiter)
87 |
88 | let contentOutput = ""
89 |
90 | for (const chunk of bufferChunks) {
91 | if (chunk.startsWith(jsonDataPrefix)) {
92 | const jsonString = chunk.slice(jsonDataPrefix.length)
93 |
94 | if (jsonString === "[DONE]") {
95 | continue
96 | }
97 |
98 | const jsonData: MessageCompletionChunk = JSON.parse(jsonString)
99 |
100 | for (const choice of jsonData.choices) {
101 | contentOutput += choice.delta.content ?? ""
102 | }
103 | } else {
104 | if (chunk) {
105 | let chunkObj: PossibleEndpointError = null
106 |
107 | try {
108 | chunkObj = JSON.parse(chunk)
109 | } catch (error) {
110 | console.error("Error parsing chunk:", chunk, error)
111 | }
112 |
113 | if (nonRetryErrorTypes.some((x) => x === chunkObj?.error?.type)) {
114 | console.error("Fatal error, not retrying:", chunk)
115 |
116 | throw chunkObj
117 | }
118 |
119 | console.error("Unexpected chunk:", chunk)
120 | }
121 | }
122 | }
123 |
124 | return contentOutput
125 | }
126 |
127 | export async function streamInference(
128 | messages: Message[],
129 | callbacks: Callbacks
130 | ): Promise {
131 | const apiKey = getOpenAIKey()
132 |
133 | const model = await getCurrentModel()
134 |
135 | if (!apiKey || !model) {
136 | return
137 | }
138 |
139 | const options: https.RequestOptions = {
140 | hostname: "api.openai.com",
141 | path: `/v1/chat/completions`,
142 | method: "POST",
143 | headers: {
144 | Authorization: `Bearer ${apiKey}`,
145 | "Content-Type": "application/json",
146 | "Transfer-Encoding": "chunked",
147 | },
148 | }
149 |
150 | let buffer = ""
151 |
152 | let _resolve: (value: string | PromiseLike) => void
153 | let _reject: (reason?: any) => void
154 |
155 | const req = https.request(options, (res) => {
156 | res.on("data", (chunk) => {
157 | const completion = parseCompletionDataChunk(chunk)
158 |
159 | if (completion) {
160 | buffer += completion
161 |
162 | callbacks.onData(completion)
163 | }
164 | })
165 |
166 | res.on("error", (error) => {
167 | if (callbacks.onError) {
168 | callbacks.onError(new Error(`Data error: ${error}`))
169 | }
170 | _reject(error)
171 | })
172 |
173 | res.on("end", () => {
174 | if (callbacks.onEnd) {
175 | callbacks.onEnd()
176 | }
177 | _resolve(buffer)
178 | })
179 | })
180 |
181 | req.on("error", (error) => {
182 | if (callbacks.onError) {
183 | callbacks.onError(new Error(`Request error: ${error}`))
184 | }
185 | _reject(error)
186 | })
187 |
188 | req.write(JSON.stringify({ model, messages, stream: true }))
189 | req.end()
190 |
191 | return new Promise((resolve, reject) => {
192 | _resolve = resolve
193 | _reject = reject
194 | })
195 | }
196 |
197 | export type Embedding = {
198 | embedding: number[]
199 | index: number
200 | object: "embedding"
201 | }
202 |
203 | export type EmbeddingResponse = {
204 | data: Embedding[]
205 | model: string
206 | object: "list"
207 | usage: {
208 | prompt_tokens: number
209 | total_tokens: number
210 | }
211 | }
212 |
213 | export const getEmbedding = async (text: string): Promise => {
214 | const resp = await fetch("https://api.openai.com/v1/embeddings", {
215 | method: "POST",
216 | headers: {
217 | "Content-Type": "application/json",
218 | Authorization: `Bearer ${getOpenAIKey()}`,
219 | },
220 | body: JSON.stringify({
221 | input: text,
222 | model: "text-embedding-3-large",
223 | }),
224 | })
225 |
226 | const data = (await resp.json()) as EmbeddingResponse
227 |
228 | return data.data[0].embedding
229 | }
230 |
231 | export const getEmbeddings = async (texts: string[]): Promise => {
232 | const resp = await fetch("https://api.openai.com/v1/embeddings", {
233 | method: "POST",
234 | headers: {
235 | "Content-Type": "application/json",
236 | Authorization: `Bearer ${getOpenAIKey()}`,
237 | },
238 | body: JSON.stringify({
239 | input: texts,
240 | model: "text-embedding-3-large",
241 | }),
242 | })
243 |
244 | const data = (await resp.json()) as EmbeddingResponse
245 |
246 | if (!data.data) {
247 | return []
248 | }
249 |
250 | return data.data.map((x) => x.embedding)
251 | }
252 |
253 | export const embedChunk = async (chunk: Chunk) => {
254 | chunk.embedding = await getEmbedding(chunk.text)
255 | }
256 |
257 | export const embedChunks = async (chunks: Chunk[]) => {
258 | const allTexts = chunks.map((chunk) => chunk.text)
259 | console.log(`Embedding chunks...`)
260 | for (let i = 0; i < allTexts.length; i += 1000) {
261 | console.log(` ${i} - ${i + 1000} of ${allTexts.length}`)
262 | const texts = allTexts.slice(i, i + 1000)
263 | const embeddings = await getEmbeddings(texts)
264 | chunks.slice(i, i + 1000).forEach((chunk, j) => {
265 | chunk.embedding = embeddings[j]
266 | })
267 | }
268 | }
269 |
--------------------------------------------------------------------------------
/apps/spellbound/src/api/pinecone.ts:
--------------------------------------------------------------------------------
1 | import { Pinecone } from "@pinecone-database/pinecone"
2 | import { getPineconeIndex } from "../environment/getPineconeIndex"
3 | import { getPineconeKey } from "../environment/getPineconeKey"
4 | import { embedChunks } from "./openai"
5 | import {
6 | Chunk,
7 | IGNORED_EXTENSIONS,
8 | chunkFile,
9 | chunkFiles,
10 | } from "../utils/chunking"
11 | import { createUUID } from "../utils/uuid"
12 | import { runGitCommand } from "../tools/git/utility/runGitCommand"
13 | import { getCurrentWorkspaceFolder } from "../utils/getCurrentWorkspaceFolder"
14 | import path, { join } from "path"
15 |
16 | const createClient = async () => {
17 | const pinecone = new Pinecone({
18 | apiKey: getPineconeKey()!,
19 | })
20 |
21 | return pinecone
22 | }
23 |
24 | const getIndex = async () => {
25 | const pinecone = await createClient()
26 | const indexName = getPineconeIndex()
27 | return pinecone.Index(indexName!)
28 | }
29 |
30 | const convertChunk = (chunk: Chunk, metadata = {}) => ({
31 | id: createUUID(),
32 | values: chunk.embedding!,
33 | metadata: {
34 | ...metadata,
35 | filename: chunk.filename,
36 | start: chunk.start,
37 | end: chunk.end,
38 | text: chunk.text,
39 | },
40 | })
41 |
42 | const upsertChunks = async (chunks: Chunk[], namespace = "", metadata = {}) => {
43 | const index = await getIndex()
44 | const vectors = chunks.map(convertChunk, metadata)
45 | console.log(`Upserting chunks:`)
46 | for (let i = 0; i < vectors.length; i += 100) {
47 | console.log(` ${i} - ${i + 100} of ${vectors.length}`)
48 | await index.upsert(vectors.slice(i, i + 100))
49 | }
50 | }
51 |
52 | export const upsertFile = async (
53 | filename: string,
54 | block_size: number,
55 | block_overlap: number,
56 | metadata: any,
57 | namespace = ""
58 | ) => {
59 | const chunks = chunkFile(filename, block_size, block_overlap)
60 | await embedChunks(chunks)
61 | await upsertChunks(chunks, namespace, metadata)
62 | }
63 |
64 | export const upsertFolder = async (
65 | folder: string,
66 | block_size: number,
67 | block_overlap: number,
68 | metadata: any,
69 | namespace = ""
70 | ) => {
71 | const files = (await runGitCommand("ls-files"))
72 | .split("\n")
73 | .slice(1, -1)
74 | .filter(
75 | (x) =>
76 | !IGNORED_EXTENSIONS.includes(path.extname(x)) &&
77 | !x.endsWith("package-lock.json")
78 | )
79 |
80 | const workspace = getCurrentWorkspaceFolder()
81 | const absoluteFiles = files.map((f) => join(workspace, f))
82 |
83 | const chunks = chunkFiles(absoluteFiles, block_size, block_overlap)
84 | await embedChunks(chunks)
85 | await upsertChunks(chunks, namespace, metadata)
86 | }
87 |
88 | export const query = async (
89 | vector: number[],
90 | topK: number,
91 | filter = undefined
92 | ) => {
93 | const client = await createClient()
94 | const indexName = getPineconeIndex()
95 | const index = client.Index(indexName!)
96 | const queryRequest = {
97 | vector,
98 | topK,
99 | filter,
100 | includeMetadata: true,
101 | includeValues: false,
102 | }
103 |
104 | return index.query(queryRequest)
105 | }
106 |
107 | export const clearNamespace = async () => {
108 | const client = await createClient()
109 | const indexName = getPineconeIndex()
110 | // const index = client.index(indexName!)
111 |
112 | // try {
113 | // await index.deleteAll()
114 | // } catch (error) {
115 | // console.error(error)
116 | // }
117 | }
118 |
119 | // run DescribeIndexStats
120 | export const indexStats = async () => {
121 | const index = await getIndex()
122 | return index.describeIndexStats()
123 | }
124 |
--------------------------------------------------------------------------------
/apps/spellbound/src/commands/indexFolder.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode"
2 | import { clearNamespace, upsertFolder } from "../api/pinecone"
3 |
4 | // path is vscode Path thing
5 | export const indexFolder = async (
6 | path: vscode.Uri,
7 | _: any,
8 | namespace: string = ""
9 | ) => {
10 | try {
11 | // if namespace is empty, prompt the user
12 | if (namespace === "") {
13 | namespace =
14 | (await vscode.window.showInputBox({
15 | prompt: "Enter a namespace for this index",
16 | placeHolder: "namespace",
17 | })) || ""
18 | }
19 |
20 | await clearNamespace()
21 | vscode.window.showInformationMessage(`Indexing ${namespace}...`)
22 |
23 | await upsertFolder(path.path, 4000, 100, {}, namespace)
24 | vscode.window.showInformationMessage("Workspace indexed!")
25 | } catch (error) {
26 | console.error(error)
27 | vscode.window.showErrorMessage("Error indexing workspace")
28 | if (error instanceof Error) {
29 | vscode.window.showErrorMessage(error.message)
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/apps/spellbound/src/commands/indexWorkspace.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode"
2 | import { clearNamespace, upsertFolder } from "../api/pinecone"
3 | import { makeAbsolute } from "../utils/paths"
4 |
5 | export const indexWorkspace = async () => {
6 | try {
7 | await clearNamespace()
8 | vscode.window.showInformationMessage("Indexing workspace...")
9 |
10 | await upsertFolder(makeAbsolute("."), 100, 10, {}, "code")
11 | vscode.window.showInformationMessage("Workspace indexed!")
12 | } catch (error) {
13 | console.error(error)
14 | vscode.window.showErrorMessage("Error indexing workspace")
15 | if (error instanceof Error) {
16 | vscode.window.showErrorMessage(error.message)
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/apps/spellbound/src/environment/getCurrentModel.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode"
2 | import { getModelList } from "./getModelList"
3 |
4 | let availableModels: string[] = []
5 |
6 | export async function getCurrentModel() {
7 | const spellboundConfig = vscode.workspace.getConfiguration("spellbound")
8 |
9 | const model = spellboundConfig.get("model") || "gpt-4"
10 |
11 | let updatedListOnThisCheck: boolean = false
12 |
13 | // Refresh the list of available models if it's empty
14 | if (availableModels.length === 0) {
15 | availableModels = await getModelList()
16 | updatedListOnThisCheck = true
17 | }
18 |
19 | // Check if the model is available
20 | if (!availableModels.includes(model)) {
21 | if (!updatedListOnThisCheck) {
22 | availableModels = await getModelList()
23 | }
24 |
25 | // If model is not available even after refreshing the list, warn the user
26 | if (!availableModels.includes(model)) {
27 | vscode.window.showWarningMessage(
28 | `Model '${model}' not found. Available models: ${availableModels.join(
29 | ", "
30 | )}\n\nPlease set the 'spellbound.model' setting to one of the available models.`
31 | )
32 | return
33 | }
34 | }
35 |
36 | return model
37 | }
38 |
--------------------------------------------------------------------------------
/apps/spellbound/src/environment/getModelList.ts:
--------------------------------------------------------------------------------
1 | import { listModels } from "../api/openai"
2 | import { getOpenAIKey } from "./getOpenAIKey"
3 |
4 | export async function getModelList() {
5 | const key = getOpenAIKey()
6 |
7 | if (key) {
8 | const models = await listModels(key)
9 |
10 | return models
11 | }
12 |
13 | return []
14 | }
15 |
--------------------------------------------------------------------------------
/apps/spellbound/src/environment/getOpenAIKey.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode"
2 |
3 | export function getOpenAIKey() {
4 | const openai_api_key =
5 | vscode.workspace
6 | .getConfiguration("spellbound")
7 | .get("openai_api_key") || process.env.OPENAI_API_KEY
8 |
9 | if (!openai_api_key) {
10 | vscode.window.showWarningMessage(
11 | "No OpenAI API key found. Please set the 'spellbound.openai_api_key' setting or set the OPENAI_API_KEY environment variable."
12 | )
13 | }
14 |
15 | return openai_api_key
16 | }
17 |
--------------------------------------------------------------------------------
/apps/spellbound/src/environment/getPineconeIndex.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode"
2 |
3 | export function getPineconeIndex() {
4 | const pinecone_index =
5 | vscode.workspace
6 | .getConfiguration("spellbound")
7 | .get("pinecone_index") || process.env.PINECONE_INDEX
8 |
9 | if (!pinecone_index) {
10 | vscode.window.showWarningMessage(
11 | "No Pinecone Index found. Please set the 'spellbound.pinecone_index' setting or set the PINECONE_INDEX environment variable."
12 | )
13 | }
14 |
15 | return pinecone_index
16 | }
17 |
--------------------------------------------------------------------------------
/apps/spellbound/src/environment/getPineconeKey.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode"
2 |
3 | export function getPineconeKey() {
4 | const pinecone_api_key =
5 | vscode.workspace
6 | .getConfiguration("spellbound")
7 | .get("pinecone_api_key") || process.env.PINECONE_API_KEY
8 |
9 | if (!pinecone_api_key) {
10 | vscode.window.showWarningMessage(
11 | "No Pinecone API key found. Please set the 'spellbound.pinecone_api_key' setting or set the PINECONE_API_KEY environment variable."
12 | )
13 | }
14 |
15 | return pinecone_api_key
16 | }
17 |
--------------------------------------------------------------------------------
/apps/spellbound/src/extension.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode"
2 | import { ChatboxViewProvider } from "./views/ChatboxViewProvider"
3 | import { indexWorkspace } from "./commands/indexWorkspace"
4 | import { indexFolder } from "./commands/indexFolder"
5 |
6 | export async function activate(context: vscode.ExtensionContext) {
7 | console.log('Your extension "spellbound" is now active!')
8 |
9 | const chatboxViewProvider = new ChatboxViewProvider(context.extensionUri)
10 |
11 | context.subscriptions.push(
12 | vscode.window.registerWebviewViewProvider(
13 | "spellbound.chat",
14 | chatboxViewProvider,
15 | {
16 | webviewOptions: {
17 | retainContextWhenHidden: true,
18 | },
19 | }
20 | )
21 | )
22 |
23 | // register command
24 | context.subscriptions.push(
25 | vscode.commands.registerCommand("spellbound.indexWorkspace", indexWorkspace)
26 | )
27 | context.subscriptions.push(
28 | vscode.commands.registerCommand("spellbound.indexFolder", indexFolder)
29 | )
30 | }
31 |
32 | // This method is called when your extension is deactivated
33 | export function deactivate() {}
34 |
--------------------------------------------------------------------------------
/apps/spellbound/src/inference/InferenceHandler.ts:
--------------------------------------------------------------------------------
1 | import { Message } from "spellbound-shared"
2 | import { streamInference } from "../api/openai"
3 |
4 | export class InferenceJob {
5 | private aborted = false
6 | buffer: string
7 |
8 | constructor(
9 | protected messages: Message[],
10 | protected onStart: () => Promise,
11 | protected onData: (output: string) => Promise,
12 | protected onEnd: (result: string) => Promise
13 | ) {
14 | this.buffer = ''
15 | this.aborted = false
16 | }
17 |
18 | abort(): void {
19 | this.aborted = true
20 | }
21 |
22 | public async start(): Promise {
23 | this.buffer = ''
24 | this.aborted = false
25 |
26 | this.onStart()
27 |
28 | await streamInference(this.messages, {
29 | onData: async (output: string) => {
30 | if (!this.aborted) {
31 | this.buffer += output
32 | await this.onData(output)
33 | }
34 | },
35 | onEnd: async () => {
36 | if (!this.aborted) {
37 | await this.onEnd(this.buffer)
38 | }
39 | },
40 | })
41 | }
42 | }
43 |
44 | export class InferenceService {
45 | private currentJob: InferenceJob | null = null
46 |
47 | infer(
48 | messages: Message[],
49 | onStart: () => Promise,
50 | onData: (output: string) => Promise,
51 | onEnd: (result: string) => Promise
52 | ) {
53 | this.abort()
54 | this.currentJob = new InferenceJob(messages, onStart, onData, onEnd)
55 | this.currentJob.start()
56 | }
57 |
58 | abort(): void {
59 | if (this.currentJob) {
60 | this.currentJob.abort()
61 | this.currentJob = null
62 | }
63 | }
64 |
65 | }
--------------------------------------------------------------------------------
/apps/spellbound/src/prompts/fillPrompt.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode"
2 | import ejs from "ejs"
3 | import { promises as fs } from "fs"
4 | import path from "path"
5 | import { AnyToolInterface } from "../tools/AnyToolInterface"
6 | import { ToolEngine } from "../tools/ToolEngine"
7 |
8 | type FillPromptParams = {
9 | codebase_description?: string | undefined
10 | language?: string | undefined
11 | task: string
12 | current_file_name?: string | undefined
13 | langcode?: string | undefined
14 | current_file_contents?: string | undefined
15 | }
16 |
17 | // render unordered list of tools
18 | function renderToolList(tools: Partial) {
19 | return Object.values(tools).map(([_, sig, doc]) => {
20 | return `- ${sig}: ${doc}`
21 | })
22 | }
23 |
24 | export async function fillPrompt(params?: Partial): Promise {
25 | params = params || {}
26 |
27 | const activeTextEditor = vscode.window.activeTextEditor
28 |
29 | // Get the workspace folder path
30 | const basePath = vscode.workspace.workspaceFolders?.[0].uri.fsPath
31 |
32 | // Getting language, current_file_name, langcode, and current_file_contents from the active text editor
33 | if (activeTextEditor) {
34 | params.language = activeTextEditor.document.languageId
35 |
36 | if (basePath) {
37 | params.current_file_name = path.relative(
38 | basePath,
39 | activeTextEditor.document.fileName
40 | )
41 | }
42 |
43 | params.langcode = activeTextEditor.document.languageId // Assuming langcode is same as languageId
44 | params.current_file_contents = activeTextEditor.document.getText()
45 | }
46 |
47 | params.language = params.language || "undefined"
48 | params.current_file_name = params.current_file_name || "undefined"
49 | params.langcode = params.langcode || "undefined"
50 | params.current_file_contents = params.current_file_contents || "undefined"
51 | params.codebase_description = "Unknown codebase."
52 |
53 | const wsFolders = vscode.workspace.workspaceFolders
54 |
55 | if (wsFolders && wsFolders.length > 0) {
56 | // Assume that the first workspace folder is the root of the codebase
57 | const workspaceFolderPath = wsFolders[0].uri.fsPath
58 |
59 | // Load codebase_description from the .spellbound/description.md file
60 | const descriptionFilePath = path.join(
61 | workspaceFolderPath,
62 | ".spellbound",
63 | "description.md"
64 | )
65 |
66 | try {
67 | params.codebase_description = await fs.readFile(
68 | descriptionFilePath,
69 | "utf-8"
70 | )
71 | } catch (error) {
72 | vscode.window.showWarningMessage(
73 | "Could not find a .spellbound/description.md file in the workspace folder. Presence of this file improves AI performance."
74 | )
75 | }
76 | }
77 |
78 | // Read file
79 | const promptFilePath = path.join(__dirname, "..", "assets", "prompt.md")
80 | const sourcePrompt = await fs.readFile(promptFilePath, "utf-8")
81 |
82 | // Fill out the prompt
83 | const filledPrompt = ejs.render(sourcePrompt, {
84 | ...params,
85 | toolList: renderToolList(ToolEngine).join("\n"),
86 | })
87 |
88 | console.log("prompt", filledPrompt)
89 |
90 | return filledPrompt
91 | }
92 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/AnyToolInterface.ts:
--------------------------------------------------------------------------------
1 | import { CatToolInterface } from "./cat/CatToolInterface"
2 | import { LsToolInterface } from "./ls/LsToolInterface"
3 | import { SearchToolInterface } from "./search/SearchToolInterface"
4 | import { WriteToolInterface } from "./write/WriteToolInterface"
5 | import { AskToolInterface } from "./ask/AskToolInterface"
6 | import { NpmToolInterface } from "./npm/NpmToolInterface"
7 | import { DoneToolInterface } from "./done/DoneToolInterface"
8 | import { StatsToolInterface } from "./stats/StatsToolInterface"
9 | import { GitToolInterface } from "./git/GitToolInterface"
10 | import { GrepToolInterface } from "./grep/GrepToolInterface"
11 | import { MoveToolInterface } from "./move/MoveToolInterface"
12 |
13 | export type AnyToolInterface =
14 | | CatToolInterface
15 | | LsToolInterface
16 | | SearchToolInterface
17 | | WriteToolInterface
18 | | GrepToolInterface
19 | | AskToolInterface
20 | | NpmToolInterface
21 | | GitToolInterface
22 | | DoneToolInterface
23 | | StatsToolInterface
24 | | MoveToolInterface
25 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/ToolEngine.ts:
--------------------------------------------------------------------------------
1 | import { askToolImpl } from "./ask/askToolImpl"
2 | import { catToolImpl } from "./cat/catToolImpl"
3 | import { doneToolImpl } from "./done/doneToolImpl"
4 | import { lsToolImpl } from "./ls/lsToolImpl"
5 | import { npmToolImpl } from "./npm/npmToolImpl"
6 | import { gitToolImpl } from "./git/gitToolImpl"
7 | import { searchToolImpl } from "./search/searchToolImpl"
8 | import { statsToolImpl } from "./stats/statsToolImpl"
9 | import { writeToolImpl } from "./write/writeToolImpl"
10 | import { diffToolImpl } from "./diff/diffToolImpl"
11 | import { grepToolImpl } from "./grep/grepToolImpl"
12 | import { moveToolImpl } from "./move/moveToolImpl"
13 |
14 |
15 | export const ToolEngine = {
16 | ask: [askToolImpl,
17 | "ask {question}",
18 | "Ask the user a question"],
19 |
20 | cat: [catToolImpl,
21 | "cat {path}",
22 | "Read the content of a file at the given path."],
23 |
24 | done: [doneToolImpl,
25 | "done",
26 | "This '## Thought' is the final chain-of-reasoning conclusion."],
27 |
28 | ls: [lsToolImpl,
29 | "ls {regex}",
30 | "List all files in the workspace with path matching the given regex."],
31 |
32 | npm: [npmToolImpl,
33 | "npm {args}",
34 | "Run an npm command (e.g., `npm i some-package`)."],
35 |
36 | git: [gitToolImpl,
37 | "git {args}",
38 | "Run an git command with optional arguments. End your commit messages with '(By SB)'"],
39 |
40 | search: [searchToolImpl,
41 | "search {topic}",
42 | "Search for a topic in the vector-embedding database."],
43 |
44 | stats: [statsToolImpl,
45 | "stats",
46 | "List available vector embedding namespaces"],
47 |
48 | write: [writeToolImpl,
49 | "write {path, contents}",
50 | "Write (or overwrite) the given contents into the specified file. Takes a long time to large."],
51 |
52 | diff: [diffToolImpl,
53 | "diff {source, patchStr}",
54 | "For _modifying_ files. Takes filename and unified diff patch string."],
55 |
56 | grep: [grepToolImpl,
57 | "grep {regex, path?}",
58 | "Perform a grep search with the specified list of glob patterns and a regex query."],
59 |
60 | move: [moveToolImpl,
61 | "move {source, dest}",
62 | "Move a file or folder from source to destination. Paths relative to workspace root."]
63 |
64 | } as const
65 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/ask/AskToolInterface.ts:
--------------------------------------------------------------------------------
1 | export type AskToolInterface = {
2 | tool: "ask"
3 | question: string
4 | }
5 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/ask/askToolImpl.ts:
--------------------------------------------------------------------------------
1 | import { AskToolInterface } from "./AskToolInterface"
2 |
3 | export async function askToolImpl(params: AskToolInterface) {
4 | return undefined
5 | }
6 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/cat/CatToolInterface.ts:
--------------------------------------------------------------------------------
1 | export type CatToolInterface = {
2 | tool: "cat"
3 | path: string
4 | }
5 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/cat/catToolImpl.ts:
--------------------------------------------------------------------------------
1 | import { promises as fs } from "fs"
2 | import path from "path"
3 | import * as vscode from "vscode"
4 | import { CatToolInterface } from "./CatToolInterface"
5 |
6 | export async function catToolImpl(params: CatToolInterface): Promise {
7 | const relativeFilePath = params.path
8 |
9 | const workspacePath = vscode.workspace.workspaceFolders?.[0].uri.fsPath
10 |
11 | if (!workspacePath) {
12 | return `ERROR: No workspace folder currently open`
13 | }
14 |
15 | const absoluteFilePath = path.join(workspacePath, relativeFilePath)
16 |
17 | try {
18 | const contents = await fs.readFile(absoluteFilePath, "utf8")
19 | return contents
20 | } catch (err) {
21 | return `ERROR: Failed to read file: ${relativeFilePath}`
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/diff/DiffToolInterface.ts:
--------------------------------------------------------------------------------
1 | export interface DiffToolInterface {
2 | source: string;
3 | patchStr: string;
4 | }
5 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/diff/diffToolImpl.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode"
2 | import fs from "fs"
3 | import path from "path"
4 |
5 | import { DiffToolInterface } from "./DiffToolInterface"
6 | import { applyPatch, parsePatch } from "diff"
7 |
8 | export async function diffToolImpl(params: DiffToolInterface): Promise {
9 | const {source, patchStr} = params
10 |
11 | try {
12 | const patches = parsePatch(patchStr)
13 | const workspaceRoot = vscode.workspace.workspaceFolders?.[0].uri.fsPath
14 | const filename = path.join(workspaceRoot || "", source)
15 | const text = fs.readFileSync(filename, "utf8")
16 | const patchedText = applyPatch(text, patches[0])
17 |
18 | if (!patchedText) {
19 | throw new Error("Failed to apply patch.")
20 | }
21 |
22 | fs.writeFileSync(filename, patchedText)
23 | return "Done."
24 |
25 | } catch (err: any) {
26 | return `ERROR: Failed to apply diff patch: ${err?.message || 'Unknown error'}`
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/done/DoneToolInterface.ts:
--------------------------------------------------------------------------------
1 | export type DoneToolInterface = {
2 | tool: "done"
3 | }
4 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/done/doneToolImpl.ts:
--------------------------------------------------------------------------------
1 | import { DoneToolInterface } from "./DoneToolInterface"
2 |
3 | export async function doneToolImpl(params: DoneToolInterface) {
4 | return undefined
5 | }
6 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/git/GitToolInterface.ts:
--------------------------------------------------------------------------------
1 | export type GitToolInterface = {
2 | tool: "git"
3 | args: string
4 | }
5 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/git/gitToolImpl.ts:
--------------------------------------------------------------------------------
1 | import { GitToolInterface } from "./GitToolInterface"
2 | import * as vscode from "vscode"
3 | import { execSync } from "child_process"
4 | import { runGitCommand } from "./utility/runGitCommand"
5 |
6 | export async function gitToolImpl(params: GitToolInterface): Promise {
7 | const { args } = params
8 |
9 | const workspacePath = vscode.workspace.workspaceFolders?.[0].uri.fsPath
10 |
11 | if (!workspacePath) {
12 | return `ERROR: No workspace folder currently open`
13 | }
14 |
15 | try {
16 | return await runGitCommand(args)
17 | } catch (err: any) {
18 | return `ERROR: Failed to execute git command: ${err?.message || 'Unknown error'}`
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/git/utility/runGitCommand.ts:
--------------------------------------------------------------------------------
1 | import { exec } from "child_process"
2 | import * as vscode from "vscode"
3 |
4 | export async function runGitCommand(
5 | args: string,
6 | ): Promise {
7 | return new Promise((resolve, reject) => {
8 | const cwd = vscode.workspace.workspaceFolders?.[0].uri.fsPath
9 | // Execute the npm command in the provided working directory
10 | exec(`git ${args}`, { cwd }, (error, stdout, stderr) => {
11 | if (error) {
12 | const errorMessage = `ERROR: Failed to run git command: ${args}\n${stdout}\n${stderr}`
13 | console.error(errorMessage)
14 | resolve(errorMessage)
15 | } else {
16 | const successMessage = `Successfully ran git command: ${args}\n${stdout}`
17 | resolve(successMessage)
18 | }
19 | })
20 | })
21 | }
22 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/grep/GrepToolInterface.ts:
--------------------------------------------------------------------------------
1 | export type GrepToolInterface = {
2 | tool: "grep",
3 | regex: string,
4 | path?: string[],
5 | }
6 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/grep/grepToolImpl.ts:
--------------------------------------------------------------------------------
1 | import { GrepToolInterface } from './GrepToolInterface'
2 | import { runGitCommand } from '../git/utility/runGitCommand'
3 |
4 |
5 | type GrepResult = {
6 | line: string
7 | lineNumber: number
8 | }
9 |
10 | export async function grepToolImpl({ regex, path }: GrepToolInterface) {
11 | const result = await runGitCommand(`grep -n --exclude-standard --no-index -e '${regex}' -- ${path || '.'}`)
12 | if (result.startsWith('ERROR')) {
13 | return "No files found."
14 | }
15 | return result
16 | .split('\n')
17 | .slice(1, -1)
18 | .join('\n') || 'No files found'
19 | }
20 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/ls/LsToolInterface.ts:
--------------------------------------------------------------------------------
1 | export type LsToolInterface = {
2 | tool: "ls"
3 | regex: string
4 | }
5 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/ls/lsToolImpl.ts:
--------------------------------------------------------------------------------
1 | import { LsToolInterface } from "./LsToolInterface"
2 | import { runGitCommand } from "../git/utility/runGitCommand"
3 |
4 | export async function lsToolImpl({ regex }: LsToolInterface): Promise {
5 | return (await runGitCommand("ls-files --exclude=node_modules --exclude-standard --deduplicate"))
6 | .split("\n")
7 | .slice(1, -1)
8 | .filter((x) => RegExp(regex).test(x))
9 | .map((x) => `- ${x}`)
10 | .join("\n") || "No files found"
11 | }
12 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/move/MoveToolInterface.ts:
--------------------------------------------------------------------------------
1 | export type MoveToolInterface = {
2 | tool: "move"
3 | source: string
4 | dest: string
5 | }
6 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/move/moveToolImpl.ts:
--------------------------------------------------------------------------------
1 | import { promises as fs } from "fs"
2 | import path from "path"
3 | import * as vscode from "vscode"
4 | import { MoveToolInterface } from "./MoveToolInterface"
5 |
6 | export async function moveToolImpl(params: MoveToolInterface): Promise {
7 | const sourcePath = params.source
8 | const destPath = params.dest
9 |
10 | const workspacePath = vscode.workspace.workspaceFolders?.[0].uri.fsPath
11 |
12 | if (!workspacePath) {
13 | return `ERROR: No workspace folder currently open`
14 | }
15 |
16 | const sourceAbsolutePath = path.join(workspacePath, sourcePath)
17 | const destinationAbsolutePath = path.join(workspacePath, destPath)
18 |
19 | try {
20 | await fs.rename(sourceAbsolutePath, destinationAbsolutePath)
21 | return `File moved from ${sourcePath} to ${destPath}`
22 | } catch (err) {
23 | return `ERROR: Failed to move file from ${sourcePath} to ${destPath}`
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/npm/NpmToolInterface.ts:
--------------------------------------------------------------------------------
1 | export type NpmToolInterface = {
2 | tool: "npm"
3 | args: string
4 | }
5 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/npm/npmToolImpl.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode"
2 | import { runNpmCommand } from "./utility/runNpmCommand"
3 | import { NpmToolInterface } from "./NpmToolInterface"
4 |
5 | export async function npmToolImpl(params: NpmToolInterface): Promise {
6 | const { args } = params
7 |
8 | // Base off of workspace directory
9 | const workspacePath = vscode.workspace.workspaceFolders?.[0].uri.fsPath
10 |
11 | if (!workspacePath) {
12 | throw new Error("No workspace folder found.")
13 | }
14 |
15 | // Use the helper function to run the npm script
16 | return await runNpmCommand(args)
17 | }
18 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/npm/utility/runNpmCommand.ts:
--------------------------------------------------------------------------------
1 | import { exec } from "child_process"
2 | import * as vscode from "vscode"
3 |
4 | export async function runNpmCommand(
5 | args: string,
6 | ): Promise {
7 | return new Promise((resolve, reject) => {
8 | // Execute the npm command in the provided working directory
9 | exec(`npm ${args}`, {
10 | // workspace root
11 | cwd: vscode.workspace.workspaceFolders?.[0].uri.fsPath,
12 | }, (error, stdout, stderr) => {
13 | if (error) {
14 | const errorMessage = `ERROR: Failed to run npm command: ${args}\n${stdout}\n${stderr}`
15 | console.error(errorMessage)
16 | resolve(errorMessage)
17 | } else {
18 | const successMessage = `Successfully ran npm command: ${args}\n${stdout}\n${stderr}`
19 | resolve(successMessage)
20 | }
21 | })
22 | })
23 | }
24 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/search/SearchToolInterface.ts:
--------------------------------------------------------------------------------
1 | export type SearchToolInterface = {
2 | tool: "search"
3 | topic: string
4 | }
5 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/search/searchToolImpl.ts:
--------------------------------------------------------------------------------
1 | import { getEmbedding } from "../../api/openai"
2 | import { query } from "../../api/pinecone"
3 |
4 | import { SearchToolInterface } from "./SearchToolInterface"
5 | import { makeRelative } from "../../utils/paths"
6 | import { ScoredPineconeRecord } from "@pinecone-database/pinecone"
7 |
8 | type Score = { max: number; avg: number }
9 |
10 | const byKey = (scores: Record, key: keyof Score) => {
11 | return Object.keys(scores).sort((a, b) => {
12 | const scoreA = scores[a]
13 | const scoreB = scores[b]
14 | return scoreB[key] - scoreA[key]
15 | })
16 | }
17 |
18 | const calculateScores = (matches: ScoredPineconeRecord[]) => {
19 | const scores: Record = {}
20 |
21 | for (const result of matches || []) {
22 | const { filename } = result.metadata as any
23 | const score = scores[filename] || { max: 0, avg: 0 }
24 | score.max = Math.max(score.max, result.score || 0)
25 | score.avg += result.score || 0
26 | scores[filename] = score
27 | }
28 |
29 | return scores
30 | }
31 |
32 | export async function searchToolImpl(params: SearchToolInterface) {
33 | const embedding = await getEmbedding(params.topic)
34 | const results = await query(embedding, 5, undefined)
35 |
36 | const scores = calculateScores(results.matches || [])
37 |
38 | const byScore = byKey(scores, "max")
39 | const byAverage = byKey(scores, "avg")
40 |
41 | const topScorePath = makeRelative(byScore[0])
42 | const topAveragePath = makeRelative(byAverage[0])
43 |
44 | let response = `Semantic search results:
45 | Top score: ${topScorePath}
46 | Top average: ${topAveragePath}`
47 |
48 | return response
49 | }
50 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/stats/StatsToolInterface.ts:
--------------------------------------------------------------------------------
1 | export type StatsToolInterface = {
2 | tool: "stats"
3 | }
4 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/stats/statsToolImpl.ts:
--------------------------------------------------------------------------------
1 | import { indexStats } from "../../api/pinecone"
2 |
3 | import { StatsToolInterface } from "./StatsToolInterface"
4 |
5 | export async function statsToolImpl(params: StatsToolInterface) {
6 | const stats = await indexStats()
7 | return JSON.stringify(stats, null, 2)
8 | }
9 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/write/WriteToolInterface.ts:
--------------------------------------------------------------------------------
1 | export type WriteToolInterface = {
2 | tool: "write"
3 | path: string
4 | contents: string
5 | }
6 |
--------------------------------------------------------------------------------
/apps/spellbound/src/tools/write/writeToolImpl.ts:
--------------------------------------------------------------------------------
1 | import { promises as fs, mkdirSync, existsSync } from "fs"
2 | import path from "path"
3 | import * as vscode from "vscode"
4 | import { WriteToolInterface } from "./WriteToolInterface"
5 |
6 | export async function writeToolImpl(
7 | params: WriteToolInterface
8 | ): Promise {
9 | const filePath = params.path
10 | const contents = params.contents
11 |
12 | // Base off of workspace directory
13 | const workspacePath = vscode.workspace.workspaceFolders?.[0].uri.fsPath
14 |
15 | if (!workspacePath) {
16 | return `ERROR: No workspace folder open`
17 | }
18 |
19 | const absoluteFilePath = path.join(workspacePath, filePath)
20 | const fileDirectory = path.dirname(absoluteFilePath)
21 |
22 | if (!existsSync(fileDirectory)) {
23 | mkdirSync(fileDirectory, { recursive: true })
24 | }
25 |
26 | try {
27 | await fs.writeFile(absoluteFilePath, contents, "utf8")
28 | return `File written to ${filePath}`
29 | } catch (err) {
30 | return `ERROR: Failed to write file: ${filePath}`
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/apps/spellbound/src/utils/chunking.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs"
2 | import path from "path"
3 | import { getCurrentWorkspaceFolder } from "./getCurrentWorkspaceFolder"
4 |
5 | export const IGNORED_EXTENSIONS = [
6 | ".png",
7 | ".jpg",
8 | ".jpeg",
9 | ".gif",
10 | ".mp3",
11 | ".mp4",
12 | ".mov",
13 | ".avi",
14 | ".zip",
15 | ".gz",
16 | ".tar",
17 | ".tgz",
18 | ".bz2",
19 | ".rar",
20 | ".7z",
21 | ".pdf",
22 | ".doc",
23 | ".docx",
24 | ".xls",
25 | ".xlsx",
26 | ".ppt",
27 | ".pptx",
28 | ".odt",
29 | ".ods",
30 | ]
31 |
32 | export type Chunk = {
33 | filename: string
34 | start: number
35 | end: number
36 | text: string
37 | embedding: number[] | null
38 | }
39 |
40 | export const chunkFile = (
41 | filename: string,
42 | block_size: number,
43 | block_overlap: number
44 | ) => {
45 | if (IGNORED_EXTENSIONS.includes(path.extname(filename))) {
46 | return []
47 | }
48 |
49 | console.log("chunking file", filename)
50 | const content = fs.readFileSync(filename, "utf8")
51 |
52 | const chunks: Chunk[] = []
53 | let start = 0
54 | let end = block_size
55 | let overlap = block_overlap
56 |
57 | while (start < content.length) {
58 | if (end > content.length) {
59 | end = content.length
60 | }
61 |
62 | const relativeFilename = path.relative(
63 | getCurrentWorkspaceFolder(),
64 | filename
65 | )
66 |
67 | chunks.push({
68 | filename: relativeFilename,
69 | start,
70 | end,
71 | text: content.substring(start, end),
72 | embedding: null,
73 | })
74 |
75 | if (end === content.length) {
76 | break
77 | }
78 |
79 | start = end - overlap
80 | end = start + block_size
81 | }
82 |
83 | return chunks
84 | }
85 |
86 | export const chunkFiles = (
87 | filenames: string[],
88 | block_size: number,
89 | block_overlap: number
90 | ) => {
91 | return filenames.flatMap((filename) =>
92 | chunkFile(filename, block_size, block_overlap)
93 | )
94 | }
95 |
96 | export const chunkFolder = (
97 | folder: string,
98 | block_size: number,
99 | block_overlap: number
100 | ): Chunk[] => {
101 | const names = fs.readdirSync(folder)
102 |
103 | // ignore hidden files and node_modules
104 | const filteredNames = names
105 | .filter((f) => !f.startsWith("."))
106 | .filter((f) => f !== "node_modules")
107 |
108 | const fullNames = filteredNames.map((f) => path.join(folder, f))
109 |
110 | const files = fullNames
111 | .filter((f) => fs.statSync(f).isFile())
112 | .filter((f) => !IGNORED_EXTENSIONS.includes(path.extname(f)))
113 |
114 | const dirs = fullNames.filter((f) => fs.statSync(f).isDirectory())
115 |
116 | const chunks = chunkFiles(files, block_size, block_overlap)
117 | const childChunks = dirs.flatMap((d) =>
118 | chunkFolder(d, block_size, block_overlap)
119 | )
120 |
121 | return chunks.concat(childChunks)
122 | }
123 |
--------------------------------------------------------------------------------
/apps/spellbound/src/utils/getCurrentWorkspaceFolder.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode"
2 |
3 | export function getCurrentWorkspaceFolder() {
4 | const workspaceFolder = vscode.workspace.workspaceFolders?.[0].uri.fsPath
5 |
6 | if (!workspaceFolder) {
7 | vscode.window.showWarningMessage("No workspace folder open")
8 | throw new Error("No workspace folder open")
9 | }
10 |
11 | return workspaceFolder
12 | }
13 |
--------------------------------------------------------------------------------
/apps/spellbound/src/utils/paths.ts:
--------------------------------------------------------------------------------
1 | import { join, relative } from "path"
2 | import { getCurrentWorkspaceFolder } from "./getCurrentWorkspaceFolder"
3 |
4 | export const makeRelative = (path: string) =>
5 | relative(getCurrentWorkspaceFolder(), path)
6 |
7 | export const makeAbsolute = (path: string) =>
8 | join(getCurrentWorkspaceFolder(), path)
9 |
--------------------------------------------------------------------------------
/apps/spellbound/src/utils/uuid.ts:
--------------------------------------------------------------------------------
1 |
2 | export const createUUID = () => {
3 | return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
4 | var r = (Math.random() * 16) | 0,
5 | v = c === "x" ? r : (r & 0x3) | 0x8
6 | return v.toString(16)
7 | })
8 | }
--------------------------------------------------------------------------------
/apps/spellbound/src/views/ChatboxViewProvider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode"
2 | import markdownit from "markdown-it"
3 | import { readFileSync } from "fs"
4 | import { Message, WebviewProcedures, createBirpc } from "spellbound-shared"
5 | import { AgentLogicHandler } from "../agent/AgentLogicHandler"
6 |
7 | export class ChatboxViewProvider implements vscode.WebviewViewProvider {
8 | constructor(private readonly extensionUri: vscode.Uri) { }
9 |
10 | private md = markdownit({
11 | html: true,
12 | breaks: true,
13 | })
14 |
15 | messagesToHumanReadable(messages: Message[]): string {
16 | return messages
17 | .map(message => `[${message.role === 'agent' ? 'Agent' : 'User'}]:\n${message.content}`)
18 | .join('\n')
19 | }
20 |
21 | resolveWebviewView(webviewView: vscode.WebviewView) {
22 | webviewView.webview.options = {
23 | enableScripts: true,
24 | localResourceRoots: [this.extensionUri],
25 | }
26 |
27 | webviewView.webview.html = this.getChatboxHtml()
28 |
29 | const agentLogicHandler = new AgentLogicHandler(
30 | createBirpc({
31 | abort: () => {
32 | agentLogicHandler.abort()
33 | },
34 | submit: async (messages: Message[]) => {
35 | agentLogicHandler.promptAgent(messages)
36 | },
37 | saveToFile: async (messages: Message[]) => {
38 | try {
39 | const saveUri = await vscode.window.showSaveDialog({
40 | filters: {
41 | "Markdown Files": ["md"],
42 | "Text Files": ["txt"]
43 | },
44 | saveLabel: "Save Chat Messages",
45 | title: "Save Chat Messages As"
46 | })
47 |
48 | if (saveUri) {
49 | const formattedMessages = this.messagesToHumanReadable(messages)
50 | await vscode.workspace.fs.writeFile(saveUri, Buffer.from(formattedMessages))
51 | vscode.window.showInformationMessage("Chat messages saved successfully.")
52 | }
53 | } catch (error) {
54 | vscode.window.showErrorMessage(`Error saving file: ${(error as Error).message}`)
55 | }
56 | }
57 | }, {
58 | post: data => {
59 | webviewView.webview.postMessage(data)
60 | },
61 | on: data => {
62 | webviewView.webview.onDidReceiveMessage(e => {
63 | data(e)
64 | })
65 | },
66 | })
67 | )
68 | }
69 |
70 | private getChatboxCss() {
71 | const stylesFilename = "static/main.css"
72 | const stylesUri = this.extensionUri.with({
73 | path: this.extensionUri.path + "/" + stylesFilename,
74 | })
75 | return readFileSync(stylesUri.fsPath, "utf8")
76 | }
77 |
78 | private getChatboxJs() {
79 | const scriptFilename = "static/main.js"
80 | const scriptUri = this.extensionUri.with({
81 | path: this.extensionUri.path + "/" + scriptFilename,
82 | })
83 | return readFileSync(scriptUri.fsPath, "utf8")
84 | }
85 |
86 | private getChatboxHtml() {
87 | const stylesText = this.getChatboxCss()
88 | const scriptText = this.getChatboxJs()
89 |
90 | return /* html */`
91 |
92 |
93 |
94 |
95 |
96 |
97 | Chatbox
98 |
110 |
111 |
112 |
113 |
116 |
117 |
118 | `
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/apps/spellbound/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "ES2020",
5 | "lib": ["ES2020"],
6 | "esModuleInterop": true,
7 | "sourceMap": true,
8 | "rootDir": "src",
9 | "strict": true
10 | },
11 | "exclude": ["node_modules", "webview"]
12 | }
13 |
--------------------------------------------------------------------------------
/apps/spellbound/webpack.config.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 |
3 | 'use strict'
4 |
5 | const fs = require('fs')
6 | const path = require('path')
7 | const CopyPlugin = require("copy-webpack-plugin")
8 |
9 | // copy ../spellbound-ui/build/static/{js,css} to ./static/
10 | const uiBuildDir = path.resolve(__dirname, '..', 'spellbound-ui', 'build')
11 | const uiStaticDir = path.resolve(uiBuildDir, 'static')
12 | const uiStaticJsDir = path.resolve(uiStaticDir, 'js')
13 | const uiStaticCssDir = path.resolve(uiStaticDir, 'css')
14 | const uiStaticJsFiles = fs.readdirSync(uiStaticJsDir)
15 | const uiStaticCssFiles = fs.readdirSync(uiStaticCssDir)
16 |
17 | const copyFile = (src, dest) => fs.copyFileSync(src, dest)
18 | const copyFiles = (srcDir, destDir, files) => files.forEach(file => copyFile(path.resolve(srcDir, file), path.resolve(destDir, file)))
19 |
20 | const staticDir = path.resolve(__dirname, 'static')
21 |
22 | if (!fs.existsSync(staticDir)) {
23 | fs.mkdirSync(staticDir)
24 | }
25 |
26 | copyFiles(uiStaticJsDir, staticDir, uiStaticJsFiles)
27 | copyFiles(uiStaticCssDir, staticDir, uiStaticCssFiles)
28 |
29 |
30 | //@ts-check
31 | /** @typedef {import('webpack').Configuration} WebpackConfig **/
32 |
33 | /** @type WebpackConfig */
34 | const extensionConfig = {
35 | target: 'node', // VS Code extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/
36 | mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
37 |
38 | entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
39 | output: {
40 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
41 | path: path.resolve(__dirname, 'dist'),
42 | filename: 'extension.js',
43 | libraryTarget: 'commonjs2'
44 | },
45 | externals: {
46 | vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/
47 | // modules added here also need to be added in the .vscodeignore file
48 | },
49 | resolve: {
50 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
51 | extensions: ['.ts', '.js']
52 | },
53 | module: {
54 | rules: [
55 | {
56 | test: /\.ts$/,
57 | exclude: /node_modules/,
58 | use: [
59 | {
60 | loader: 'ts-loader'
61 | }
62 | ]
63 | }
64 | ]
65 | },
66 | plugins: [
67 | // copy ./static/{js,css} to dist/
68 | new CopyPlugin({
69 | patterns: [
70 | { from: path.resolve(__dirname, 'static/main.js'), to: 'static/main.js' },
71 | { from: path.resolve(__dirname, 'static/main.css'), to: 'static/main.css' },
72 | ],
73 | }),
74 | ],
75 | devtool: 'nosources-source-map',
76 | infrastructureLogging: {
77 | level: "log", // enables logging required for problem matchers
78 | },
79 | }
80 | module.exports = [ extensionConfig ]
--------------------------------------------------------------------------------
/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agentitive/spellbound/99c6e70f2aa74322d074d8b88c0b1cdef2d7acdf/assets/logo.png
--------------------------------------------------------------------------------
/common/config/rush/.npmrc:
--------------------------------------------------------------------------------
1 | # Rush uses this file to configure the NPM package registry during installation. It is applicable
2 | # to PNPM, NPM, and Yarn package managers. It is used by operations such as "rush install",
3 | # "rush update", and the "install-run.js" scripts.
4 | #
5 | # NOTE: The "rush publish" command uses .npmrc-publish instead.
6 | #
7 | # Before invoking the package manager, Rush will copy this file to the folder where installation
8 | # is performed. The copied file will omit any config lines that reference environment variables
9 | # that are undefined in that session; this avoids problems that would otherwise result due to
10 | # a missing variable being replaced by an empty string.
11 | #
12 | # * * * SECURITY WARNING * * *
13 | #
14 | # It is NOT recommended to store authentication tokens in a text file on a lab machine, because
15 | # other unrelated processes may be able to read the file. Also, the file may persist indefinitely,
16 | # for example if the machine loses power. A safer practice is to pass the token via an
17 | # environment variable, which can be referenced from .npmrc using ${} expansion. For example:
18 | #
19 | # //registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}
20 | #
21 | registry=https://registry.npmjs.org/
22 | always-auth=false
23 |
--------------------------------------------------------------------------------
/common/config/rush/.npmrc-publish:
--------------------------------------------------------------------------------
1 | # This config file is very similar to common/config/rush/.npmrc, except that .npmrc-publish
2 | # is used by the "rush publish" command, as publishing often involves different credentials
3 | # and registries than other operations.
4 | #
5 | # Before invoking the package manager, Rush will copy this file to "common/temp/publish-home/.npmrc"
6 | # and then temporarily map that folder as the "home directory" for the current user account.
7 | # This enables the same settings to apply for each project folder that gets published. The copied file
8 | # will omit any config lines that reference environment variables that are undefined in that session;
9 | # this avoids problems that would otherwise result due to a missing variable being replaced by
10 | # an empty string.
11 | #
12 | # * * * SECURITY WARNING * * *
13 | #
14 | # It is NOT recommended to store authentication tokens in a text file on a lab machine, because
15 | # other unrelated processes may be able to read the file. Also, the file may persist indefinitely,
16 | # for example if the machine loses power. A safer practice is to pass the token via an
17 | # environment variable, which can be referenced from .npmrc using ${} expansion. For example:
18 | #
19 | # //registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}
20 | #
21 |
--------------------------------------------------------------------------------
/common/config/rush/.pnpmfile.cjs:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * When using the PNPM package manager, you can use pnpmfile.js to workaround
5 | * dependencies that have mistakes in their package.json file. (This feature is
6 | * functionally similar to Yarn's "resolutions".)
7 | *
8 | * For details, see the PNPM documentation:
9 | * https://pnpm.js.org/docs/en/hooks.html
10 | *
11 | * IMPORTANT: SINCE THIS FILE CONTAINS EXECUTABLE CODE, MODIFYING IT IS LIKELY TO INVALIDATE
12 | * ANY CACHED DEPENDENCY ANALYSIS. After any modification to pnpmfile.js, it's recommended to run
13 | * "rush update --full" so that PNPM will recalculate all version selections.
14 | */
15 | module.exports = {
16 | hooks: {
17 | readPackage
18 | }
19 | };
20 |
21 | /**
22 | * This hook is invoked during installation before a package's dependencies
23 | * are selected.
24 | * The `packageJson` parameter is the deserialized package.json
25 | * contents for the package that is about to be installed.
26 | * The `context` parameter provides a log() function.
27 | * The return value is the updated object.
28 | */
29 | function readPackage(packageJson, context) {
30 |
31 | // // The karma types have a missing dependency on typings from the log4js package.
32 | // if (packageJson.name === '@types/karma') {
33 | // context.log('Fixed up dependencies for @types/karma');
34 | // packageJson.dependencies['log4js'] = '0.6.38';
35 | // }
36 |
37 | return packageJson;
38 | }
39 |
--------------------------------------------------------------------------------
/common/config/rush/artifactory.json:
--------------------------------------------------------------------------------
1 | /**
2 | * This configuration file manages Rush integration with JFrog Artifactory services.
3 | * More documentation is available on the Rush website: https://rushjs.io
4 | */
5 | {
6 | "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/artifactory.schema.json",
7 |
8 | "packageRegistry": {
9 | /**
10 | * (Required) Set this to "true" to enable Rush to manage tokens for an Artifactory NPM registry.
11 | * When enabled, "rush install" will automatically detect when the user's ~/.npmrc
12 | * authentication token is missing or expired. And "rush setup" will prompt the user to
13 | * renew their token.
14 | *
15 | * The default value is false.
16 | */
17 | "enabled": false,
18 |
19 | /**
20 | * (Required) Specify the URL of your NPM registry. This is the same URL that appears in
21 | * your .npmrc file. It should look something like this example:
22 | *
23 | * https://your-company.jfrog.io/your-project/api/npm/npm-private/
24 | */
25 | "registryUrl": "",
26 |
27 | /**
28 | * A list of custom strings that "rush setup" should add to the user's ~/.npmrc file at the time
29 | * when the token is updated. This could be used for example to configure the company registry
30 | * to be used whenever NPM is invoked as a standalone command (but it's not needed for Rush
31 | * operations like "rush add" and "rush install", which get their mappings from the monorepo's
32 | * common/config/rush/.npmrc file).
33 | *
34 | * NOTE: The ~/.npmrc settings are global for the user account on a given machine, so be careful
35 | * about adding settings that may interfere with other work outside the monorepo.
36 | */
37 | "userNpmrcLinesToAdd": [
38 | // "@example:registry=https://your-company.jfrog.io/your-project/api/npm/npm-private/"
39 | ],
40 |
41 | /**
42 | * (Required) Specifies the URL of the Artifactory control panel where the user can generate
43 | * an API key. This URL is printed after the "visitWebsite" message.
44 | * It should look something like this example: https://your-company.jfrog.io/
45 | * Specify an empty string to suppress this line entirely.
46 | */
47 | "artifactoryWebsiteUrl": "",
48 |
49 | /**
50 | * Uncomment this line to specify the type of credential to save in the user's ~/.npmrc file.
51 | * The default is "password", which means the user's API token will be traded in for an
52 | * npm password specific to that registry. Optionally you can specify "authToken", which
53 | * will save the user's API token as credentials instead.
54 | */
55 | // "credentialType": "password",
56 |
57 | /**
58 | * These settings allow the "rush setup" interactive prompts to be customized, for
59 | * example with messages specific to your team or configuration. Specify an empty string
60 | * to suppress that message entirely.
61 | */
62 | "messageOverrides": {
63 | /**
64 | * Overrides the message that normally says:
65 | * "This monorepo consumes packages from an Artifactory private NPM registry."
66 | */
67 | // "introduction": "",
68 |
69 | /**
70 | * Overrides the message that normally says:
71 | * "Please contact the repository maintainers for help with setting up an Artifactory user account."
72 | */
73 | // "obtainAnAccount": "",
74 |
75 | /**
76 | * Overrides the message that normally says:
77 | * "Please open this URL in your web browser:"
78 | *
79 | * The "artifactoryWebsiteUrl" string is printed after this message.
80 | */
81 | // "visitWebsite": "",
82 |
83 | /**
84 | * Overrides the message that normally says:
85 | * "Your user name appears in the upper-right corner of the JFrog website."
86 | */
87 | // "locateUserName": "",
88 |
89 | /**
90 | * Overrides the message that normally says:
91 | * "Click 'Edit Profile' on the JFrog website. Click the 'Generate API Key'
92 | * button if you haven't already done so previously."
93 | */
94 | // "locateApiKey": ""
95 |
96 | /**
97 | * Overrides the message that normally prompts:
98 | * "What is your Artifactory user name?"
99 | */
100 | // "userNamePrompt": ""
101 |
102 | /**
103 | * Overrides the message that normally prompts:
104 | * "What is your Artifactory API key?"
105 | */
106 | // "apiKeyPrompt": ""
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/common/config/rush/build-cache.json:
--------------------------------------------------------------------------------
1 | /**
2 | * This configuration file manages Rush's build cache feature.
3 | * More documentation is available on the Rush website: https://rushjs.io
4 | */
5 | {
6 | "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/build-cache.schema.json",
7 |
8 | /**
9 | * (Required) EXPERIMENTAL - Set this to true to enable the build cache feature.
10 | *
11 | * See https://rushjs.io/pages/maintainer/build_cache/ for details about this experimental feature.
12 | */
13 | "buildCacheEnabled": false,
14 |
15 | /**
16 | * (Required) Choose where project build outputs will be cached.
17 | *
18 | * Possible values: "local-only", "azure-blob-storage", "amazon-s3"
19 | */
20 | "cacheProvider": "local-only",
21 |
22 | /**
23 | * Setting this property overrides the cache entry ID. If this property is set, it must contain
24 | * a [hash] token.
25 | *
26 | * Other available tokens:
27 | * - [projectName]
28 | * - [projectName:normalize]
29 | * - [phaseName]
30 | * - [phaseName:normalize]
31 | * - [phaseName:trimPrefix]
32 | */
33 | // "cacheEntryNamePattern": "[projectName:normalize]-[phaseName:normalize]-[hash]"
34 |
35 | /**
36 | * Use this configuration with "cacheProvider"="azure-blob-storage"
37 | */
38 | "azureBlobStorageConfiguration": {
39 | /**
40 | * (Required) The name of the the Azure storage account to use for build cache.
41 | */
42 | // "storageAccountName": "example",
43 |
44 | /**
45 | * (Required) The name of the container in the Azure storage account to use for build cache.
46 | */
47 | // "storageContainerName": "my-container",
48 |
49 | /**
50 | * The Azure environment the storage account exists in. Defaults to AzurePublicCloud.
51 | *
52 | * Possible values: "AzurePublicCloud", "AzureChina", "AzureGermany", "AzureGovernment"
53 | */
54 | // "azureEnvironment": "AzurePublicCloud",
55 |
56 | /**
57 | * An optional prefix for cache item blob names.
58 | */
59 | // "blobPrefix": "my-prefix",
60 |
61 | /**
62 | * If set to true, allow writing to the cache. Defaults to false.
63 | */
64 | // "isCacheWriteAllowed": true
65 | },
66 |
67 | /**
68 | * Use this configuration with "cacheProvider"="amazon-s3"
69 | */
70 | "amazonS3Configuration": {
71 | /**
72 | * (Required unless s3Endpoint is specified) The name of the bucket to use for build cache.
73 | * Example: "my-bucket"
74 | */
75 | // "s3Bucket": "my-bucket",
76 |
77 | /**
78 | * (Required unless s3Bucket is specified) The Amazon S3 endpoint of the bucket to use for build cache.
79 | * This should not include any path; use the s3Prefix to set the path.
80 | * Examples: "my-bucket.s3.us-east-2.amazonaws.com" or "http://localhost:9000"
81 | */
82 | // "s3Endpoint": "https://my-bucket.s3.us-east-2.amazonaws.com",
83 |
84 | /**
85 | * (Required) The Amazon S3 region of the bucket to use for build cache.
86 | * Example: "us-east-1"
87 | */
88 | // "s3Region": "us-east-1",
89 |
90 | /**
91 | * An optional prefix ("folder") for cache items. It should not start with "/".
92 | */
93 | // "s3Prefix": "my-prefix",
94 |
95 | /**
96 | * If set to true, allow writing to the cache. Defaults to false.
97 | */
98 | // "isCacheWriteAllowed": true
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/common/config/rush/common-versions.json:
--------------------------------------------------------------------------------
1 | /**
2 | * This configuration file specifies NPM dependency version selections that affect all projects
3 | * in a Rush repo. More documentation is available on the Rush website: https://rushjs.io
4 | */
5 | {
6 | "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/common-versions.schema.json",
7 |
8 | /**
9 | * A table that specifies a "preferred version" for a given NPM package. This feature is typically used
10 | * to hold back an indirect dependency to a specific older version, or to reduce duplication of indirect dependencies.
11 | *
12 | * The "preferredVersions" value can be any SemVer range specifier (e.g. "~1.2.3"). Rush injects these values into
13 | * the "dependencies" field of the top-level common/temp/package.json, which influences how the package manager
14 | * will calculate versions. The specific effect depends on your package manager. Generally it will have no
15 | * effect on an incompatible or already constrained SemVer range. If you are using PNPM, similar effects can be
16 | * achieved using the pnpmfile.js hook. See the Rush documentation for more details.
17 | *
18 | * After modifying this field, it's recommended to run "rush update --full" so that the package manager
19 | * will recalculate all version selections.
20 | */
21 | "preferredVersions": {
22 | /**
23 | * When someone asks for "^1.0.0" make sure they get "1.2.3" when working in this repo,
24 | * instead of the latest version.
25 | */
26 | // "some-library": "1.2.3"
27 | },
28 |
29 | /**
30 | * When set to true, for all projects in the repo, all dependencies will be automatically added as preferredVersions,
31 | * except in cases where different projects specify different version ranges for a given dependency. For older
32 | * package managers, this tended to reduce duplication of indirect dependencies. However, it can sometimes cause
33 | * trouble for indirect dependencies with incompatible peerDependencies ranges.
34 | *
35 | * The default value is true. If you're encountering installation errors related to peer dependencies,
36 | * it's recommended to set this to false.
37 | *
38 | * After modifying this field, it's recommended to run "rush update --full" so that the package manager
39 | * will recalculate all version selections.
40 | */
41 | // "implicitlyPreferredVersions": false,
42 |
43 | /**
44 | * The "rush check" command can be used to enforce that every project in the repo must specify
45 | * the same SemVer range for a given dependency. However, sometimes exceptions are needed.
46 | * The allowedAlternativeVersions table allows you to list other SemVer ranges that will be
47 | * accepted by "rush check" for a given dependency.
48 | *
49 | * IMPORTANT: THIS TABLE IS FOR *ADDITIONAL* VERSION RANGES THAT ARE ALTERNATIVES TO THE
50 | * USUAL VERSION (WHICH IS INFERRED BY LOOKING AT ALL PROJECTS IN THE REPO).
51 | * This design avoids unnecessary churn in this file.
52 | */
53 | "allowedAlternativeVersions": {
54 | /**
55 | * For example, allow some projects to use an older TypeScript compiler
56 | * (in addition to whatever "usual" version is being used by other projects in the repo):
57 | */
58 | // "typescript": [
59 | // "~2.4.0"
60 | // ]
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/common/config/rush/experiments.json:
--------------------------------------------------------------------------------
1 | /**
2 | * This configuration file allows repo maintainers to enable and disable experimental
3 | * Rush features. More documentation is available on the Rush website: https://rushjs.io
4 | */
5 | {
6 | "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/experiments.schema.json",
7 |
8 | /**
9 | * By default, 'rush install' passes --no-prefer-frozen-lockfile to 'pnpm install'.
10 | * Set this option to true to pass '--frozen-lockfile' instead for faster installs.
11 | */
12 | // "usePnpmFrozenLockfileForRushInstall": true,
13 |
14 | /**
15 | * By default, 'rush update' passes --no-prefer-frozen-lockfile to 'pnpm install'.
16 | * Set this option to true to pass '--prefer-frozen-lockfile' instead to minimize shrinkwrap changes.
17 | */
18 | // "usePnpmPreferFrozenLockfileForRushUpdate": true,
19 |
20 | /**
21 | * If using the 'preventManualShrinkwrapChanges' option, restricts the hash to only include the layout of external dependencies.
22 | * Used to allow links between workspace projects or the addition/removal of references to existing dependency versions to not
23 | * cause hash changes.
24 | */
25 | // "omitImportersFromPreventManualShrinkwrapChanges": true,
26 |
27 | /**
28 | * If true, the chmod field in temporary project tar headers will not be normalized.
29 | * This normalization can help ensure consistent tarball integrity across platforms.
30 | */
31 | // "noChmodFieldInTarHeaderNormalization": true,
32 |
33 | /**
34 | * If true, build caching will respect the allowWarningsInSuccessfulBuild flag and cache builds with warnings.
35 | * This will not replay warnings from the cached build.
36 | */
37 | // "buildCacheWithAllowWarningsInSuccessfulBuild": true,
38 |
39 | /**
40 | * If true, the phased commands feature is enabled. To use this feature, create a "phased" command
41 | * in common/config/rush/command-line.json.
42 | */
43 | // "phasedCommands": true,
44 |
45 | /**
46 | * If true, perform a clean install after when running `rush install` or `rush update` if the
47 | * `.npmrc` file has changed since the last install.
48 | */
49 | // "cleanInstallAfterNpmrcChanges": true,
50 |
51 | /**
52 | * If true, print the outputs of shell commands defined in event hooks to the console.
53 | */
54 | // "printEventHooksOutputToConsole": true
55 | }
56 |
--------------------------------------------------------------------------------
/common/config/rush/pnpm-config.json:
--------------------------------------------------------------------------------
1 | /**
2 | * This configuration file provides settings specific to the PNPM package manager.
3 | * More documentation is available on the Rush website: https://rushjs.io
4 | */
5 | {
6 | "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/pnpm-config.schema.json",
7 |
8 | /**
9 | * If true, then `rush install` and `rush update` will use the PNPM workspaces feature
10 | * to perform the install, instead of the old model where Rush generated the symlinks
11 | * for each projects's node_modules folder.
12 | *
13 | * When using workspaces, Rush will generate a `common/temp/pnpm-workspace.yaml` file referencing
14 | * all local projects to install. Rush will also generate a `.pnpmfile.cjs` shim which implements
15 | * Rush-specific features such as preferred versions. The user's `common/config/rush/.pnpmfile.cjs`
16 | * is invoked by the shim.
17 | *
18 | * This option is strongly recommended. The default value is false.
19 | */
20 | "useWorkspaces": true,
21 |
22 | /**
23 | * If true, then Rush will add the `--strict-peer-dependencies` command-line parameter when
24 | * invoking PNPM. This causes `rush update` to fail if there are unsatisfied peer dependencies,
25 | * which is an invalid state that can cause build failures or incompatible dependency versions.
26 | * (For historical reasons, JavaScript package managers generally do not treat this invalid
27 | * state as an error.)
28 | *
29 | * PNPM documentation: https://pnpm.io/npmrc#strict-peer-dependencies
30 | *
31 | * The default value is false to avoid legacy compatibility issues.
32 | * It is strongly recommended to set `strictPeerDependencies=true`.
33 | */
34 | // "strictPeerDependencies": true,
35 |
36 | /**
37 | * Environment variables that will be provided to PNPM.
38 | */
39 | // "environmentVariables": {
40 | // "NODE_OPTIONS": {
41 | // "value": "--max-old-space-size=4096",
42 | // "override": false
43 | // }
44 | // },
45 |
46 | /**
47 | * Specifies the location of the PNPM store. There are two possible values:
48 | *
49 | * - `local` - use the `pnpm-store` folder in the current configured temp folder:
50 | * `common/temp/pnpm-store` by default.
51 | * - `global` - use PNPM's global store, which has the benefit of being shared
52 | * across multiple repo folders, but the disadvantage of less isolation for builds
53 | * (for example, bugs or incompatibilities when two repos use different releases of PNPM)
54 | *
55 | * In both cases, the store path can be overridden by the environment variable `RUSH_PNPM_STORE_PATH`.
56 | *
57 | * The default value is `local`.
58 | */
59 | // "pnpmStore": "global",
60 |
61 | /**
62 | * If true, then `rush install` will report an error if manual modifications
63 | * were made to the PNPM shrinkwrap file without running `rush update` afterwards.
64 | *
65 | * This feature protects against accidental inconsistencies that may be introduced
66 | * if the PNPM shrinkwrap file (`pnpm-lock.yaml`) is manually edited. When this
67 | * feature is enabled, `rush update` will append a hash to the file as a YAML comment,
68 | * and then `rush update` and `rush install` will validate the hash. Note that this
69 | * does not prohibit manual modifications, but merely requires `rush update` be run
70 | * afterwards, ensuring that PNPM can report or repair any potential inconsistencies.
71 | *
72 | * To temporarily disable this validation when invoking `rush install`, use the
73 | * `--bypass-policy` command-line parameter.
74 | *
75 | * The default value is false.
76 | */
77 | // "preventManualShrinkwrapChanges": true,
78 |
79 | /**
80 | * The "globalOverrides" setting provides a simple mechanism for overriding version selections
81 | * for all dependencies of all projects in the monorepo workspace. The settings are copied
82 | * into the `pnpm.overrides` field of the `common/temp/package.json` file that is generated
83 | * by Rush during installation.
84 | *
85 | * Order of precedence: `.pnpmfile.cjs` has the highest precedence, followed by
86 | * `unsupportedPackageJsonSettings`, `globalPeerDependencyRules`, `globalPackageExtensions`,
87 | * and `globalOverrides` has lowest precedence.
88 | *
89 | * PNPM documentation: https://pnpm.io/package_json#pnpmoverrides
90 | */
91 | "globalOverrides": {
92 | // "example1": "^1.0.0",
93 | // "example2": "npm:@company/example2@^1.0.0"
94 | },
95 |
96 | /**
97 | * The `globalPeerDependencyRules` setting provides various settings for suppressing validation errors
98 | * that are reported during installation with `strictPeerDependencies=true`. The settings are copied
99 | * into the `pnpm.peerDependencyRules` field of the `common/temp/package.json` file that is generated
100 | * by Rush during installation.
101 | *
102 | * Order of precedence: `.pnpmfile.cjs` has the highest precedence, followed by
103 | * `unsupportedPackageJsonSettings`, `globalPeerDependencyRules`, `globalPackageExtensions`,
104 | * and `globalOverrides` has lowest precedence.
105 | *
106 | * https://pnpm.io/package_json#pnpmpeerdependencyrules
107 | */
108 | "globalPeerDependencyRules": {
109 | // "ignoreMissing": ["@eslint/*"],
110 | // "allowedVersions": { "react": "17" },
111 | // "allowAny": ["@babel/*"]
112 | },
113 |
114 | /**
115 | * The `globalPackageExtension` setting provides a way to patch arbitrary package.json fields
116 | * for any PNPM dependency of the monorepo. The settings are copied into the `pnpm.packageExtensions`
117 | * field of the `common/temp/package.json` file that is generated by Rush during installation.
118 | * The `globalPackageExtension` setting has similar capabilities as `.pnpmfile.cjs` but without
119 | * the downsides of an executable script (nondeterminism, unreliable caching, performance concerns).
120 | *
121 | * Order of precedence: `.pnpmfile.cjs` has the highest precedence, followed by
122 | * `unsupportedPackageJsonSettings`, `globalPeerDependencyRules`, `globalPackageExtensions`,
123 | * and `globalOverrides` has lowest precedence.
124 | *
125 | * PNPM documentation: https://pnpm.io/package_json#pnpmpackageextensions
126 | */
127 | "globalPackageExtensions": {
128 | // "fork-ts-checker-webpack-plugin": {
129 | // "dependencies": {
130 | // "@babel/core": "1"
131 | // },
132 | // "peerDependencies": {
133 | // "eslint": ">= 6"
134 | // },
135 | // "peerDependenciesMeta": {
136 | // "eslint": {
137 | // "optional": true
138 | // }
139 | // }
140 | // }
141 | },
142 |
143 | /**
144 | * The `globalNeverBuiltDependencies` setting suppresses the `preinstall`, `install`, and `postinstall`
145 | * lifecycle events for the specified NPM dependencies. This is useful for scripts with poor practices
146 | * such as downloading large binaries without retries or attempting to invoke OS tools such as
147 | * a C++ compiler. (PNPM's terminology refers to these lifecycle events as "building" a package;
148 | * it has nothing to do with build system operations such as `rush build` or `rushx build`.)
149 | * The settings are copied into the `pnpm.neverBuiltDependencies` field of the `common/temp/package.json`
150 | * file that is generated by Rush during installation.
151 | *
152 | * PNPM documentation: https://pnpm.io/package_json#pnpmneverbuiltdependencies
153 | */
154 | "globalNeverBuiltDependencies": [
155 | // "fsevents"
156 | ],
157 |
158 | /**
159 | * The `globalAllowedDeprecatedVersions` setting suppresses installation warnings for package
160 | * versions that the NPM registry reports as being deprecated. This is useful if the
161 | * deprecated package is an indirect dependency of an external package that has not released a fix.
162 | * The settings are copied into the `pnpm.allowedDeprecatedVersions` field of the `common/temp/package.json`
163 | * file that is generated by Rush during installation.
164 | *
165 | * PNPM documentation: https://pnpm.io/package_json#pnpmalloweddeprecatedversions
166 | *
167 | * If you are working to eliminate a deprecated version, it's better to specify `allowedDeprecatedVersions`
168 | * in the package.json file for individual Rush projects.
169 | */
170 | "globalAllowedDeprecatedVersions": {
171 | // "request": "*"
172 | },
173 |
174 |
175 | /**
176 | * (THIS FIELD IS MACHINE GENERATED) The "globalPatchedDependencies" field is updated automatically
177 | * by the `rush-pnpm patch-commit` command. It is a dictionary, where the key is an NPM package name
178 | * and exact version, and the value is a relative path to the associated patch file.
179 | *
180 | * PNPM documentation: https://pnpm.io/package_json#pnpmpatcheddependencies
181 | */
182 | "globalPatchedDependencies": { },
183 |
184 | /**
185 | * (USE AT YOUR OWN RISK) This is a free-form property bag that will be copied into
186 | * the `common/temp/package.json` file that is generated by Rush during installation.
187 | * This provides a way to experiment with new PNPM features. These settings will override
188 | * any other Rush configuration associated with a given JSON field except for `.pnpmfile.cjs`.
189 | *
190 | * USAGE OF THIS SETTING IS NOT SUPPORTED BY THE RUSH MAINTAINERS AND MAY CAUSE RUSH
191 | * TO MALFUNCTION. If you encounter a missing PNPM setting that you believe should
192 | * be supported, please create a GitHub issue or PR. Note that Rush does not aim to
193 | * support every possible PNPM setting, but rather to promote a battle-tested installation
194 | * strategy that is known to provide a good experience for large teams with lots of projects.
195 | */
196 | "unsupportedPackageJsonSettings": {
197 | // "dependencies": {
198 | // "not-a-good-practice": "*"
199 | // },
200 | // "scripts": {
201 | // "do-something": "echo Also not a good practice"
202 | // },
203 | // "pnpm": { "futurePnpmFeature": true }
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/common/config/rush/repo-state.json:
--------------------------------------------------------------------------------
1 | // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush.
2 | {
3 | "preferredVersionsHash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f"
4 | }
5 |
--------------------------------------------------------------------------------
/common/config/rush/rush-plugins.json:
--------------------------------------------------------------------------------
1 | /**
2 | * This configuration file manages Rush's plugin feature.
3 | */
4 | {
5 | "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush-plugins.schema.json",
6 | "plugins": [
7 | /**
8 | * Each item configures a plugin to be loaded by Rush.
9 | */
10 | // {
11 | // /**
12 | // * The name of the NPM package that provides the plugin.
13 | // */
14 | // "packageName": "@scope/my-rush-plugin",
15 | // /**
16 | // * The name of the plugin. This can be found in the "pluginName"
17 | // * field of the "rush-plugin-manifest.json" file in the NPM package folder.
18 | // */
19 | // "pluginName": "my-plugin-name",
20 | // /**
21 | // * The name of a Rush autoinstaller that will be used for installation, which
22 | // * can be created using "rush init-autoinstaller". Add the plugin's NPM package
23 | // * to the package.json "dependencies" of your autoinstaller, then run
24 | // * "rush update-autoinstaller".
25 | // */
26 | // "autoinstallerName": "rush-plugins"
27 | // }
28 | ]
29 | }
--------------------------------------------------------------------------------
/common/config/rush/version-policies.json:
--------------------------------------------------------------------------------
1 | /**
2 | * This is configuration file is used for advanced publishing configurations with Rush.
3 | * More documentation is available on the Rush website: https://rushjs.io
4 | */
5 |
6 | /**
7 | * A list of version policy definitions. A "version policy" is a custom package versioning
8 | * strategy that affects "rush change", "rush version", and "rush publish". The strategy applies
9 | * to a set of projects that are specified using the "versionPolicyName" field in rush.json.
10 | */
11 | [
12 | // {
13 | // /**
14 | // * (Required) Indicates the kind of version policy being defined ("lockStepVersion" or "individualVersion").
15 | // *
16 | // * The "lockStepVersion" mode specifies that the projects will use "lock-step versioning". This
17 | // * strategy is appropriate for a set of packages that act as selectable components of a
18 | // * unified product. The entire set of packages are always published together, and always share
19 | // * the same NPM version number. When the packages depend on other packages in the set, the
20 | // * SemVer range is usually restricted to a single version.
21 | // */
22 | // "definitionName": "lockStepVersion",
23 | //
24 | // /**
25 | // * (Required) The name that will be used for the "versionPolicyName" field in rush.json.
26 | // * This name is also used command-line parameters such as "--version-policy"
27 | // * and "--to-version-policy".
28 | // */
29 | // "policyName": "MyBigFramework",
30 | //
31 | // /**
32 | // * (Required) The current version. All packages belonging to the set should have this version
33 | // * in the current branch. When bumping versions, Rush uses this to determine the next version.
34 | // * (The "version" field in package.json is NOT considered.)
35 | // */
36 | // "version": "1.0.0",
37 | //
38 | // /**
39 | // * (Required) The type of bump that will be performed when publishing the next release.
40 | // * When creating a release branch in Git, this field should be updated according to the
41 | // * type of release.
42 | // *
43 | // * Valid values are: "prerelease", "release", "minor", "patch", "major"
44 | // */
45 | // "nextBump": "prerelease",
46 | //
47 | // /**
48 | // * (Optional) If specified, all packages in the set share a common CHANGELOG.md file.
49 | // * This file is stored with the specified "main" project, which must be a member of the set.
50 | // *
51 | // * If this field is omitted, then a separate CHANGELOG.md file will be maintained for each
52 | // * package in the set.
53 | // */
54 | // "mainProject": "my-app",
55 | //
56 | // /**
57 | // * (Optional) If enabled, the "rush change" command will prompt the user for their email address
58 | // * and include it in the JSON change files. If an organization maintains multiple repos, tracking
59 | // * this contact information may be useful for a service that automatically upgrades packages and
60 | // * needs to notify engineers whose change may be responsible for a downstream build break. It might
61 | // * also be useful for crediting contributors. Rush itself does not do anything with the collected
62 | // * email addresses. The default value is "false".
63 | // */
64 | // // "includeEmailInChangeFile": true
65 | // },
66 | //
67 | // {
68 | // /**
69 | // * (Required) Indicates the kind of version policy being defined ("lockStepVersion" or "individualVersion").
70 | // *
71 | // * The "individualVersion" mode specifies that the projects will use "individual versioning".
72 | // * This is the typical NPM model where each package has an independent version number
73 | // * and CHANGELOG.md file. Although a single CI definition is responsible for publishing the
74 | // * packages, they otherwise don't have any special relationship. The version bumping will
75 | // * depend on how developers answer the "rush change" questions for each package that
76 | // * is changed.
77 | // */
78 | // "definitionName": "individualVersion",
79 | //
80 | // "policyName": "MyRandomLibraries",
81 | //
82 | // /**
83 | // * (Optional) This can be used to enforce that all packages in the set must share a common
84 | // * major version number, e.g. because they are from the same major release branch.
85 | // * It can also be used to discourage people from accidentally making "MAJOR" SemVer changes
86 | // * inappropriately. The minor/patch version parts will be bumped independently according
87 | // * to the types of changes made to each project, according to the "rush change" command.
88 | // */
89 | // "lockedMajor": 3,
90 | //
91 | // /**
92 | // * (Optional) When publishing is managed by Rush, by default the "rush change" command will
93 | // * request changes for any projects that are modified by a pull request. These change entries
94 | // * will produce a CHANGELOG.md file. If you author your CHANGELOG.md manually or announce updates
95 | // * in some other way, set "exemptFromRushChange" to true to tell "rush change" to ignore the projects
96 | // * belonging to this version policy.
97 | // */
98 | // "exemptFromRushChange": false,
99 | //
100 | // // "includeEmailInChangeFile": true
101 | // }
102 | ]
103 |
--------------------------------------------------------------------------------
/common/git-hooks/commit-msg.sample:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # This is an example Git hook for use with Rush. To enable this hook, rename this file
4 | # to "commit-msg" and then run "rush install", which will copy it from common/git-hooks
5 | # to the .git/hooks folder.
6 | #
7 | # TO LEARN MORE ABOUT GIT HOOKS
8 | #
9 | # The Git documentation is here: https://git-scm.com/docs/githooks
10 | # Some helpful resources: https://githooks.com
11 | #
12 | # ABOUT THIS EXAMPLE
13 | #
14 | # The commit-msg hook is called by "git commit" with one argument, the name of the file
15 | # that has the commit message. The hook should exit with non-zero status after issuing
16 | # an appropriate message if it wants to stop the commit. The hook is allowed to edit
17 | # the commit message file.
18 |
19 | # This example enforces that commit message should contain a minimum amount of
20 | # description text.
21 | if [ `cat $1 | wc -w` -lt 3 ]; then
22 | echo ""
23 | echo "Invalid commit message: The message must contain at least 3 words."
24 | exit 1
25 | fi
26 |
--------------------------------------------------------------------------------
/common/scripts/install-run-rush-pnpm.js:
--------------------------------------------------------------------------------
1 | // THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED.
2 | //
3 | // This script is intended for usage in an automated build environment where the Rush command may not have
4 | // been preinstalled, or may have an unpredictable version. This script will automatically install the version of Rush
5 | // specified in the rush.json configuration file (if not already installed), and then pass a command-line to the
6 | // rush-pnpm command.
7 | //
8 | // An example usage would be:
9 | //
10 | // node common/scripts/install-run-rush-pnpm.js pnpm-command
11 | //
12 | // For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/
13 |
14 | /******/ (() => { // webpackBootstrap
15 | /******/ "use strict";
16 | var __webpack_exports__ = {};
17 | /*!*****************************************************!*\
18 | !*** ./lib-esnext/scripts/install-run-rush-pnpm.js ***!
19 | \*****************************************************/
20 |
21 | // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
22 | // See the @microsoft/rush package's LICENSE file for license information.
23 | require('./install-run-rush');
24 | //# sourceMappingURL=install-run-rush-pnpm.js.map
25 | module.exports = __webpack_exports__;
26 | /******/ })()
27 | ;
28 | //# sourceMappingURL=install-run-rush-pnpm.js.map
--------------------------------------------------------------------------------
/common/scripts/install-run-rushx.js:
--------------------------------------------------------------------------------
1 | // THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED.
2 | //
3 | // This script is intended for usage in an automated build environment where the Rush command may not have
4 | // been preinstalled, or may have an unpredictable version. This script will automatically install the version of Rush
5 | // specified in the rush.json configuration file (if not already installed), and then pass a command-line to the
6 | // rushx command.
7 | //
8 | // An example usage would be:
9 | //
10 | // node common/scripts/install-run-rushx.js custom-command
11 | //
12 | // For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/
13 |
14 | /******/ (() => { // webpackBootstrap
15 | /******/ "use strict";
16 | var __webpack_exports__ = {};
17 | /*!*************************************************!*\
18 | !*** ./lib-esnext/scripts/install-run-rushx.js ***!
19 | \*************************************************/
20 |
21 | // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
22 | // See the @microsoft/rush package's LICENSE file for license information.
23 | require('./install-run-rush');
24 | //# sourceMappingURL=install-run-rushx.js.map
25 | module.exports = __webpack_exports__;
26 | /******/ })()
27 | ;
28 | //# sourceMappingURL=install-run-rushx.js.map
--------------------------------------------------------------------------------
/rigs/eslint-plugin-local/README.md:
--------------------------------------------------------------------------------
1 | # eslint-plugin-local
2 |
3 | Linting configuration for solid projects
4 |
5 | ## Installation
6 |
7 | You'll first need to install [ESLint](https://eslint.org/):
8 |
9 | ```sh
10 | npm i eslint --save-dev
11 | ```
12 |
13 | Next, install `eslint-plugin-local`:
14 |
15 | ```sh
16 | npm install eslint-plugin-local --save-dev
17 | ```
18 |
19 | ## Usage
20 |
21 | Add `local` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix:
22 |
23 | ```json
24 | {
25 | "plugins": [
26 | "local"
27 | ]
28 | }
29 | ```
30 |
31 |
32 |
--------------------------------------------------------------------------------
/rigs/eslint-plugin-local/lib/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview Linting configuration for solid projects
3 | * @author Dustin
4 | */
5 | "use strict"
6 |
7 | //------------------------------------------------------------------------------
8 | // Requirements
9 | //------------------------------------------------------------------------------
10 |
11 | const requireIndex = require("requireindex")
12 | const path = require("path")
13 | //------------------------------------------------------------------------------
14 | // Plugin Definition
15 | //------------------------------------------------------------------------------
16 |
17 | module.exports = {
18 | configs: {
19 | node: {
20 | ignorePatterns: [
21 | "node_modules",
22 | "dist",
23 | "docs",
24 | "deploy",
25 | ],
26 | extends: [
27 | "eslint:recommended",
28 | "plugin:@typescript-eslint/recommended"
29 | ],
30 | parser: "@typescript-eslint/parser",
31 | parserOptions: {
32 | project: ["./tsconfig.json"],
33 | sourceType: "module",
34 | ecmaFeatures: {
35 | modules: true,
36 | },
37 | extraFileExtensions: [".md", ".json"],
38 | },
39 | env: {
40 | node: true,
41 | es6: true,
42 | },
43 | plugins: [
44 | "@typescript-eslint",
45 | "import",
46 | "import-newlines",
47 | "simple-import-sort",
48 | "unused-imports",
49 | "decorator-position",
50 | ],
51 | rules: {
52 | indent: ["error", 4, { SwitchCase: 1 }],
53 | semi: ["error", "never"],
54 | camelcase: "off",
55 | "no-constant-condition": "off",
56 | "no-unused-vars": "off",
57 | "no-invalid-this": "off",
58 | "no-useless-escape": "off",
59 | "no-useless-constructor": "off",
60 |
61 | "@typescript-eslint/no-floating-promises": "error",
62 | "@typescript-eslint/no-explicit-any": "off",
63 | "@typescript-eslint/ban-types": "off",
64 | "@typescript-eslint/no-empty-function": "off",
65 | "@typescript-eslint/no-var-requires": "off",
66 | "@typescript-eslint/explicit-member-accessibility": [
67 | "error",
68 | {
69 | overrides: {
70 | accessors: "off",
71 | constructors: "no-public",
72 | methods: "off",
73 | properties: "explicit",
74 | parameterProperties: "off",
75 | },
76 | },
77 | ],
78 | "@typescript-eslint/no-invalid-this": ["error"],
79 | "@typescript-eslint/no-shadow": ["warn"],
80 | "@typescript-eslint/interface-name-prefix": "off",
81 | "@typescript-eslint/array-type": [
82 | "error",
83 | { default: "array-simple" },
84 | ],
85 | "@typescript-eslint/no-use-before-define": "off",
86 | "@typescript-eslint/explicit-member-accessibility": "off",
87 | "@typescript-eslint/explicit-function-return-type": "off",
88 | "@typescript-eslint/no-non-null-assertion": "off",
89 | "@typescript-eslint/no-unused-vars": "error",
90 | "@typescript-eslint/explicit-module-boundary-types": "off",
91 |
92 | "unused-imports/no-unused-imports": "error",
93 | "import/no-duplicates": "error",
94 | "import/first": "error",
95 | "import/newline-after-import": [
96 | "error",
97 | {
98 | count: 2,
99 | },
100 | ],
101 | "import-newlines/enforce": [
102 | "error",
103 | {
104 | semi: false,
105 | maxLen: 80,
106 | items: 4,
107 | forceSingleLine: false,
108 | },
109 | ],
110 | "simple-import-sort/imports": "error",
111 | "simple-import-sort/exports": "error",
112 |
113 | "decorator-position/decorator-position": [
114 | "error",
115 | {
116 | properties: "above",
117 | },
118 | ],
119 | },
120 | overrides: [
121 | {
122 | files: ["src/**/*.spec.ts"],
123 | env: {
124 | jest: true,
125 | node: true
126 | },
127 | parserOptions: {
128 | project: ["tsconfig.json"],
129 | sourceType: "module",
130 | ecmaFeatures: {
131 | modules: true,
132 | },
133 | extraFileExtensions: [".md", ".json"],
134 | },
135 | },
136 | ],
137 | },
138 | },
139 | }
140 |
--------------------------------------------------------------------------------
/rigs/eslint-plugin-local/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eslint-plugin-local",
3 | "version": "0.0.1",
4 | "main": "./lib/index.js",
5 | "exports": "./lib/index.js",
6 | "scripts": {
7 | "build": "",
8 | "lint": "eslint ."
9 | },
10 | "dependencies": {
11 | "@typescript-eslint/eslint-plugin": "^5.56.0",
12 | "@typescript-eslint/parser": "^5.56.0",
13 | "requireindex": "^1.2.0"
14 | },
15 | "devDependencies": {
16 | "eslint": "^8.36.0",
17 | "eslint-plugin-eslint-plugin": "^5.0.0",
18 | "eslint-plugin-node": "^11.1.0",
19 | "eslint-plugin-decorator-position": "^5.0.1",
20 | "eslint-plugin-import": "^2.26.0",
21 | "eslint-plugin-import-newlines": "^1.2.3",
22 | "eslint-plugin-simple-import-sort": "^8.0.0",
23 | "eslint-plugin-unused-imports": "^2.0.0",
24 | "mocha": "^10.0.0"
25 | },
26 | "engines": {
27 | "node": "^14.17.0 || ^16.0.0 || >= 18.0.0"
28 | },
29 | "peerDependencies": {
30 | "eslint": ">=7"
31 | },
32 | "license": "ISC"
33 | }
34 |
--------------------------------------------------------------------------------
/rigs/node-rig/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-rig",
3 | "version": "0.0.1",
4 | "description": "A rig package for Node.js projects that build using Heft",
5 | "license": "MIT",
6 | "scripts": {
7 | "build": "",
8 | "_phase:build": ""
9 | },
10 | "peerDependencies": {
11 | "@rushstack/heft": "^0.48.7"
12 | },
13 | "dependencies": {
14 | "eslint": "^8.36.0",
15 | "jest-environment-node": "~27.4.2",
16 | "typescript": "~5.0.3"
17 | },
18 | "devDependencies": {
19 | "@rushstack/heft": "0.48.7",
20 | "eslint": "^8.36.0",
21 | "typescript": "~5.0.3"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/rigs/node-rig/profiles/default/tsconfig-base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2019",
4 | "module": "commonjs",
5 | "declaration": true,
6 | "declarationMap": true,
7 | "sourceMap": true,
8 | "removeComments": true,
9 | "strict": true,
10 | "noImplicitReturns": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "esModuleInterop": true,
13 | "forceConsistentCasingInFileNames": true,
14 | "typeRoots": [
15 | "../../../../node_modules/@types",
16 | ],
17 | "pretty": true,
18 | "stripInternal": true,
19 | "preserveSymlinks": true,
20 | "outDir": "../../../../dist",
21 | "sourceRoot": "../../../../src",
22 | "incremental": true,
23 | "resolveJsonModule": true,
24 | "types": [
25 | "node"
26 | ],
27 | "experimentalDecorators": true,
28 | "emitDecoratorMetadata": true,
29 | "skipLibCheck": true,
30 | "lib": ["ES2021.WeakRef"]
31 | },
32 | "include": [
33 | "../../../../src",
34 | ],
35 | "exclude": [
36 | "../../../../node_modules"
37 | ]
38 | }
--------------------------------------------------------------------------------
/rigs/repo-scripts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "repo-scripts",
3 | "version": "0.0.1",
4 | "author": "John Chung",
5 | "main": "dist/index.js",
6 | "types": "dist/index.d.ts",
7 | "scripts": {
8 | "build": "",
9 | "generate-snippets": "ts-node src/generateSnippets.ts",
10 | "audit": "ts-node src/audit.ts",
11 | "versions": "ts-node src/versions.ts"
12 | },
13 | "dependencies": {
14 | "@microsoft/rush-lib": "5.82.1",
15 | "source-map-support": "^0.5.20",
16 | "colors": "^1.4.0",
17 | "table": "^6.8.1"
18 | },
19 | "devDependencies": {
20 | "@types/node": "~18.15.11",
21 | "@types/source-map-support": "^0.5.4",
22 | "@typescript-eslint/eslint-plugin": "^5.56.0",
23 | "@typescript-eslint/parser": "^5.56.0",
24 | "types-package-json": "^2.0.39",
25 | "node-rig": "workspace:^0.0.1",
26 | "eslint-plugin-local": "workspace:^0.0.1",
27 | "eslint": "^8.36.0",
28 | "eslint-plugin-import": "^2.26.0",
29 | "eslint-plugin-import-newlines": "^1.2.3",
30 | "eslint-plugin-json": "^3.1.0",
31 | "eslint-plugin-promise": "^6.0.1",
32 | "eslint-plugin-simple-import-sort": "^8.0.0",
33 | "eslint-plugin-unused-imports": "^2.0.0",
34 | "prettier": "^2.7.1",
35 | "ts-node": "^10.9.1",
36 | "ts-node-dev": "^2.0.0",
37 | "typescript": "~5.0.3"
38 | },
39 | "engines": {
40 | "node": "^14.17.0 || ^16.0.0 || >= 18.0.0"
41 | },
42 | "peerDependencies": {
43 | "eslint": ">=7"
44 | },
45 | "license": "ISC"
46 | }
47 |
--------------------------------------------------------------------------------
/rigs/repo-scripts/src/audit.ts:
--------------------------------------------------------------------------------
1 | import { green, red, yellow } from "colors";
2 | import { table } from "table";
3 | import { getRemotePackageDependencies } from "./utils";
4 | import { allLibraries } from "./utils";
5 |
6 | type PackageInfo = {
7 | name: string;
8 | published: boolean;
9 | problems: string[];
10 | }
11 |
12 | async function checkPackage(packageName: string) {
13 | const dependencies = await getRemotePackageDependencies(packageName);
14 |
15 | if (!dependencies) {
16 | return {
17 | name: packageName,
18 | published: false,
19 | problems: []
20 | }
21 | }
22 |
23 | const problems = Object.entries(dependencies)
24 | .filter(([depName]) => depName.startsWith("workspace:"))
25 | .map(([depName]) => depName)
26 |
27 | return {
28 | name: packageName,
29 | published: true,
30 | problems
31 | }
32 | }
33 |
34 | function sortPackages(packages: PackageInfo[]) {
35 | return packages.sort((a, b) => {
36 | if (a.published && !b.published) {
37 | return -1;
38 | }
39 | if (!a.published && b.published) {
40 | return 1;
41 | }
42 | return a.name.localeCompare(b.name);
43 | });
44 | }
45 |
46 | function formatPackage(packageInfo: PackageInfo) {
47 | const { name, published, problems } = packageInfo;
48 | const title = problems.length ? red(name) : published ? green(name) : yellow(name);
49 | return [title, published ? problems.join(", ") : "Not published."]
50 | }
51 |
52 | async function main() {
53 | const projects = allLibraries()
54 | const infos = await Promise.all(projects.map(p => checkPackage(p.packageName)));
55 | const sorted = sortPackages(infos);
56 | const rows = sorted.map(formatPackage);
57 | console.log("Workspace dependency problems:")
58 | console.log(table([
59 | ["Package", "Problems"],
60 | ...rows
61 | ]));
62 | }
63 |
64 | main();
--------------------------------------------------------------------------------
/rigs/repo-scripts/src/bumpVersions.ts:
--------------------------------------------------------------------------------
1 | import { RushConfigurationProject } from "@microsoft/rush-lib"
2 | import { readFileSync, writeFileSync } from "fs"
3 | import { allApps, allLibraries } from "./utils"
4 | import type { PackageJson } from 'types-package-json';
5 | import { execSync } from "child_process"
6 |
7 |
8 | const depTypes: ((keyof PackageJson) & ('dependencies' | 'devDependencies' | 'peerDependencies' | 'optionalDependencies'))[] = [
9 | "dependencies",
10 | "devDependencies",
11 | "peerDependencies",
12 | "optionalDependencies"
13 | ]
14 |
15 | function readPackageJson(lib: RushConfigurationProject) {
16 | const file = readFileSync(`${lib.publishFolder}/package.json`, "utf8")
17 | const packageJson = JSON.parse(file) as PackageJson
18 | return packageJson
19 | }
20 |
21 | function bumpDeps(libs: string[], versionNumber: string, packageJson: PackageJson, depType: keyof PackageJson & ('dependencies' | 'devDependencies' | 'peerDependencies' | 'optionalDependencies')) {
22 | if (!packageJson[depType]) {
23 | return
24 | }
25 | const deps = packageJson[depType]
26 | const libDeps = Object.keys(deps!).filter((dep) => libs.includes(dep))
27 | // Bump versions of the deps
28 | libDeps.forEach((dep) => {
29 | deps![dep] = `workspace:^${versionNumber}`
30 | })
31 | }
32 |
33 | function commitChanges(msg: string) {
34 | return `git add --all && git commit -m "${msg}"`
35 | }
36 |
37 | function bumpLibVersion(libPath: string, versionNumber: string) {
38 | return `cd libs/${libPath} && pnpm version ${versionNumber}`
39 | }
40 |
41 | function getAllLibsAndApps(libs: RushConfigurationProject[], apps: RushConfigurationProject[]) {
42 | return [...libs, ...apps]
43 | }
44 |
45 | function getPackageNames(libs: RushConfigurationProject[]) {
46 | return libs.map((lib) => readPackageJson(lib).name)
47 | }
48 |
49 | function getAllPackageNames(libs: RushConfigurationProject[], apps: RushConfigurationProject[]) {
50 | const allLibNames = getPackageNames(libs)
51 | const allAppNames = getPackageNames(apps)
52 |
53 | return [...allLibNames, ...allAppNames]
54 | }
55 |
56 | function run() {
57 | const versionNumber = process.argv[3]
58 | const libs = allLibraries()
59 | const apps = allApps()
60 |
61 | const libsAndApps = getAllLibsAndApps(libs, apps)
62 | const allPackageNames = getAllPackageNames(libs, apps)
63 |
64 | // Bump all solid deps to the new version in each solid lib
65 | for (const lib of libsAndApps) {
66 |
67 | const packageJson = readPackageJson(lib)
68 |
69 | depTypes.forEach((depType) => bumpDeps(allPackageNames, versionNumber, packageJson, depType))
70 | writeFileSync(`${lib.publishFolder}/package.json`, JSON.stringify(packageJson, null, 4))
71 | }
72 |
73 | execSync(
74 | commitChanges(`Bump all solid deps to ${versionNumber}`),
75 | {
76 | stdio: "inherit"
77 | }
78 | )
79 | // Bump lib versions
80 | for (const lib of libs) {
81 | execSync(
82 | `${bumpLibVersion(lib.unscopedTempProjectName, versionNumber)} &&
83 | cd ../../`,
84 | {
85 | stdio: "inherit"
86 | }
87 | )
88 | }
89 |
90 | execSync(
91 | commitChanges(`Bump all libs to ${versionNumber}`),
92 | {
93 | stdio: "inherit"
94 | }
95 | )
96 | }
97 |
98 | run()
--------------------------------------------------------------------------------
/rigs/repo-scripts/src/generateSnippets.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import { parse, resolve } from "path";
3 |
4 | export function generateSnippets() {
5 | const snippetsFiles = readSnippetsDirectory()
6 | const snippetDefinitions = generateSnippetDefinitions(snippetsFiles)
7 | const snippetsContents = buildSnippetsContents(snippetDefinitions)
8 | const snippetsFilepath = getSnippetsFilepath()
9 | fs.writeFileSync(snippetsFilepath, snippetsContents);
10 | }
11 |
12 | function readSnippetsDirectory() {
13 | const snippetsDirectory = resolve("../../", ".vscode/snippets")
14 | const snippetsFiles = fs.readdirSync(snippetsDirectory)
15 | return snippetsFiles
16 | }
17 |
18 | function generateSnippetDefinitions(snippetsFiles: string[]): Record {
19 | return snippetsFiles.reduce(parseSnippet, {}) as Record
20 | }
21 |
22 | function parseSnippet(allSnippetDefinitions: {}, filename: string) {
23 | const [prefix, description, ...lines] = readAndParseSnippetFile(filename)
24 |
25 | const cleanedPrefix = extractPrefix(prefix)
26 | const cleanedDescription = extractDescription(description)
27 | const snippetTemplateBody = generateSnippetTemplateBody(cleanedPrefix, cleanedDescription, lines);
28 | const snippetName = parse(filename).name;
29 |
30 | return { ...allSnippetDefinitions, [snippetName]: snippetTemplateBody };
31 | }
32 |
33 | function readAndParseSnippetFile(filename: string) {
34 | const snippetText = fs.readFileSync(`../../.vscode/snippets/${filename}`, "utf8");
35 | const [prefix, description, ...lines] = snippetText.split("\n");
36 | return [prefix, description, ...lines]
37 | }
38 |
39 | function extractPrefix(prefix: string) {
40 | return prefix.replace(/^(\/\/\s*)?prefix\:\s*/, "")
41 | }
42 |
43 | function extractDescription(description: string) {
44 | return description.replace(/^(\/\/\s*)?description\:\s*/, "")
45 | }
46 |
47 | function generateSnippetTemplateBody(prefix: string, description: string, body: string[]) {
48 | return {
49 | scope: "javascript,typescript",
50 | prefix,
51 | body,
52 | description,
53 | };
54 | }
55 |
56 | function buildSnippetsContents(snippetDefinitions: Record) {
57 | return JSON.stringify(snippetDefinitions, null, 2);
58 | }
59 |
60 | function getSnippetsFilepath() {
61 | return resolve("../../", ".vscode/solid-monorepo.code-snippets")
62 | }
63 |
64 | generateSnippets()
--------------------------------------------------------------------------------
/rigs/repo-scripts/src/publishLibrary.ts:
--------------------------------------------------------------------------------
1 | import { allLibraries } from "./utils"
2 | import { execSync } from "child_process"
3 |
4 |
5 | function getPackagePath(packageName: string) {
6 | const library = allLibraries().find((lib) => lib.packageName === packageName)
7 |
8 | if (!library) {
9 | throw new Error(`Could not find library named: ${packageName}`)
10 | }
11 |
12 | return library.publishFolder
13 |
14 | }
15 |
16 | function saveBackupTsconfig(libraryPath: string) {
17 | return `cp ${libraryPath}/tsconfig.json ~/tmpfile`
18 | }
19 |
20 | function useTsconfigTemplate(libraryPath: string) {
21 | return `cp rigs/repo-scripts/templates/tsconfig.json ${libraryPath}/tsconfig.json`
22 | }
23 |
24 | function addPublishArtifact(libraryPath: string) {
25 | return `cd ${libraryPath} && git add --all && git commit -m "Add publish artifact"`
26 | }
27 |
28 | function executePublish() {
29 | return `pnpm publish`
30 | }
31 |
32 | function removePublishArtifact() {
33 | return `mv ~/tmpfile tsconfig.json && git add --all && git commit -m "Remove publish artifact" && git push`
34 | }
35 |
36 | function publishPackage(libraryPath: string) {
37 | execSync(
38 | `${saveBackupTsconfig(libraryPath)} &&
39 | ${useTsconfigTemplate(libraryPath)} &&
40 | ${addPublishArtifact(libraryPath)} &&
41 | ${executePublish()} &&
42 | ${removePublishArtifact()}`,
43 | {
44 | stdio: "inherit"
45 | }
46 | )
47 | }
48 |
49 | function run() {
50 | const packagePath = getPackagePath(`@volley/${process.argv[3]}`)
51 | publishPackage(packagePath)
52 | }
53 |
54 | run()
--------------------------------------------------------------------------------
/rigs/repo-scripts/src/publishLibs.ts:
--------------------------------------------------------------------------------
1 | import { execSync } from "child_process"
2 | import { allLibraries } from "./utils"
3 |
4 |
5 | function executePublish(libPath: string) {
6 | return `cd libs/${libPath} && pnpm publish`
7 | }
8 |
9 | function run() {
10 | const libs = allLibraries()
11 |
12 | for (const lib of libs) {
13 | execSync(
14 | `${executePublish(lib.unscopedTempProjectName)} &&
15 | cd ../../`,
16 | {
17 | stdio: "inherit"
18 | }
19 | )
20 | }
21 | }
22 |
23 | run()
--------------------------------------------------------------------------------
/rigs/repo-scripts/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './npm';
2 | export * from './reflect';
3 |
--------------------------------------------------------------------------------
/rigs/repo-scripts/src/utils/npm/PackageMetadata.ts:
--------------------------------------------------------------------------------
1 | export type PackageMetadata = {
2 | _id: string
3 | _rev: string
4 | name: string
5 | description: string
6 | "dist-tags": {
7 | latest: string
8 | } & Record
9 | version: string
10 | versions: Record
11 | dependencies: Record
12 | peerDependencies: Record
13 | devDependencies: Record
14 | scripts: Record
15 | }
--------------------------------------------------------------------------------
/rigs/repo-scripts/src/utils/npm/getRemotePackageDependencies.ts:
--------------------------------------------------------------------------------
1 | import { getRemotePackageMetadata } from "./getRemotePackageMetadata";
2 |
3 |
4 | export async function getRemotePackageDependencies(packageName: string) {
5 | const metadata = await getRemotePackageMetadata(packageName);
6 | return metadata?.dependencies || null;
7 | }
--------------------------------------------------------------------------------
/rigs/repo-scripts/src/utils/npm/getRemotePackageMetadata.ts:
--------------------------------------------------------------------------------
1 | import { exec } from "child_process";
2 | import { PackageMetadata } from "./PackageMetadata";
3 |
4 | const cache = new Map();
5 |
6 | export async function getRemotePackageMetadata(packageName: string): Promise {
7 | if (cache.has(packageName)) {
8 | return cache.get(packageName)!;
9 | }
10 |
11 | return new Promise((resolve) => {
12 | exec(`npm view ${packageName} --json`, (err, stdout) => {
13 | if (err) {
14 | resolve(null);
15 | }
16 | try {
17 | const metadata = JSON.parse(stdout)
18 | cache.set(packageName, metadata);
19 | resolve(metadata as PackageMetadata);
20 | } catch (e) {
21 | resolve(null);
22 | }
23 | });
24 | });
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/rigs/repo-scripts/src/utils/npm/getRemotePackageVersion.ts:
--------------------------------------------------------------------------------
1 | import { getRemotePackageMetadata } from "./getRemotePackageMetadata";
2 |
3 |
4 | export async function getRemotePackageVersion(packageName: string) {
5 | const metadata = await getRemotePackageMetadata(packageName);
6 | return metadata?.version || null;
7 | }
--------------------------------------------------------------------------------
/rigs/repo-scripts/src/utils/npm/index.ts:
--------------------------------------------------------------------------------
1 | export * from './PackageMetadata';
2 | export * from './getRemotePackageDependencies';
3 | export * from './getRemotePackageMetadata';
4 | export * from './getRemotePackageVersion';
5 |
--------------------------------------------------------------------------------
/rigs/repo-scripts/src/utils/reflect/allApps.ts:
--------------------------------------------------------------------------------
1 | import { allProjects } from "./allProjects";
2 |
3 | export function allApps() {
4 | const projects = allProjects()
5 | return projects.filter(p => p.projectRelativeFolder.startsWith("apps"));
6 | }
--------------------------------------------------------------------------------
/rigs/repo-scripts/src/utils/reflect/allLibraries.ts:
--------------------------------------------------------------------------------
1 | import { allProjects } from "./allProjects";
2 |
3 | export function allLibraries() {
4 | const projects = allProjects()
5 | return projects.filter(p => p.projectRelativeFolder.startsWith("libs"));
6 | }
--------------------------------------------------------------------------------
/rigs/repo-scripts/src/utils/reflect/allProjects.ts:
--------------------------------------------------------------------------------
1 | import { RushConfiguration } from "@microsoft/rush-lib";
2 |
3 | export function allProjects() {
4 | const conf = RushConfiguration.loadFromDefaultLocation({
5 | startingFolder: process.cwd()
6 | });
7 | return conf.projects
8 | }
--------------------------------------------------------------------------------
/rigs/repo-scripts/src/utils/reflect/index.ts:
--------------------------------------------------------------------------------
1 | export * from './allLibraries';
2 | export * from './allProjects';
3 | export * from './allApps';
4 |
--------------------------------------------------------------------------------
/rigs/repo-scripts/src/versions.ts:
--------------------------------------------------------------------------------
1 | import { RushConfiguration, RushConfigurationProject as RCP } from "@microsoft/rush-lib"
2 | import { green, yellow } from "colors";
3 | import { table } from "table";
4 | import { allLibraries, getRemotePackageVersion } from "./utils";
5 |
6 | type PackageInfo = {
7 | name: string;
8 | localVersion: string;
9 | publishedVersion: string;
10 | }
11 |
12 | async function getPackageVersions(project: RCP): Promise {
13 | const localVersion = project.packageJson.version;
14 | const remoteVersion = await getRemotePackageVersion(project.packageName);
15 | const publishedVersion = remoteVersion || "Not published";
16 | return {
17 | name: project.packageName,
18 | localVersion,
19 | publishedVersion
20 | };
21 | }
22 |
23 | function toArray(info: PackageInfo) {
24 | const { name, localVersion, publishedVersion } = info;
25 | const title = localVersion === publishedVersion ? green(name) : yellow(name);
26 | return [title, localVersion, publishedVersion];
27 | }
28 |
29 | function toTable(packages: PackageInfo[]) {
30 | const rows = packages.map(toArray);
31 | return table([
32 | ["Package", "Local", "Published"],
33 | ...rows
34 | ])
35 | }
36 |
37 | async function main() {
38 | const libs = allLibraries();
39 | const packages = await Promise.all(libs.map(getPackageVersions));
40 | console.log(toTable(packages));
41 | }
42 |
43 | main();
--------------------------------------------------------------------------------
/rigs/repo-scripts/templates/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2019",
4 | "module": "commonjs",
5 | "declaration": true,
6 | "declarationMap": true,
7 | "sourceMap": true,
8 | "removeComments": true,
9 | "strict": true,
10 | "noImplicitReturns": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "esModuleInterop": true,
13 | "forceConsistentCasingInFileNames": true,
14 | "pretty": true,
15 | "stripInternal": true,
16 | "preserveSymlinks": true,
17 | "outDir": "./dist",
18 | "incremental": true,
19 | "resolveJsonModule": true,
20 | "types": [
21 | "node",
22 | "jest",
23 | "reflect-metadata"
24 | ],
25 | "experimentalDecorators": true,
26 | "emitDecoratorMetadata": true,
27 | "skipLibCheck": true,
28 | "lib": ["ES2021.WeakRef"]
29 | },
30 | "include": [
31 | "./src",
32 | ],
33 | "exclude": [
34 | "./node_modules",
35 | "src/**/*.test.ts"
36 | ]
37 | }
--------------------------------------------------------------------------------
/rigs/repo-scripts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./node_modules/node-rig/profiles/default/tsconfig-base.json",
3 | "exclude": [
4 | "node_modules"
5 | ]
6 | }
--------------------------------------------------------------------------------